How to write a generic code to make async call to Rest Service
-
Thank you Benjamin, Even making the change will create a strong bond with todoitem. My intention is to make it generalised so that I can make a single method for making calls for different URL's and get data for different class object As I have stated earlier I don't want to write the same code for a different URL request.
Not a Benjamin, that's a quote. You need to tack down the real basic fundamentals first; generics in C# are first class members and almost trivial. But if you'd rather walk before crawling:
public async T RefreshData(string url)
{
try
{
var response = await client.GetAsync (url);
if (response.IsSuccessStatusCode) {
var content = await response.Content.ReadAsStringAsync ();
var items = JsonConvert.DeserializeObject<T>(content);
if(items != null)
{
return items;
}
throw new Exception($"Service did not provide a response formatted as type: {typeof(T)}");
}else{
throw new Exception($"Service request failure. Code: {response.StatusCode}");
}
}
catch (Exception ex)
{
Debug.WriteLine ($"ERROR {ex.Message}");
}
}The $"" is the newfangled string interpolation BTW. Call with:
var myList = RefreshData>("http ://localhost:9054/TotoItem");
I had to throw an extra space in the string as the C# preformat is throwing in an anchor tag.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
-
Well, you're returning List, which is different from Task>. It's also worth noting that your method, as written, will run synchronously despite your *Async() naming. Convert the return type to List and rename your function and see what happens. You likely also want to define variables in the method scope for Items and RestUrl. You also appear to have a typo in a string constant. Check the RestUrl variable.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
Nathan Minier wrote:
you're returning List<todoitem>, which is different from Task<List<todoitem>>.
It's an
async
method, so that's perfectly normal. :)Nathan Minier wrote:
your method, as written, will run synchronously
Nope:
var response = await client.GetAsync (uri);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Nathan Minier wrote:
you're returning List<todoitem>, which is different from Task<List<todoitem>>.
It's an
async
method, so that's perfectly normal. :)Nathan Minier wrote:
your method, as written, will run synchronously
Nope:
var response = await client.GetAsync (uri);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Nathan Minier wrote:
you're returning List<todoitem>, which is different from Task<List<todoitem>>.
It's an
async
method, so that's perfectly normal. :)Nathan Minier wrote:
your method, as written, will run synchronously
Nope:
var response = await client.GetAsync (uri);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
I'm sorry Richard, that's not correct. Using the async keyword can mean that you want to return a Task that is not yet resolved, but doesn't necessarily mean that. It can simply mean that you're coalescing an asynchronous operation into a synchronous context, which is precisely what "await" does. Using the await keyword means that you want to use the value provided by Task.Result, not the Task itself. It blocks execution of the method (ie runs synchronously) until the awaited Task is completed. That means that while the method is flagged as async, it resolves async operations into a synchronous context. I understand that the terminology is a bit misleading and the documentation does not help. But the fact is that if you're blocking the thread, you are running synchronously. If the method were run asynchronously, it would look more like this:
public async Task RefreshData(string url)
{
return Task.Run(()=>
{
var response = await client.GetAsync (url);
if (response.IsSuccessStatusCode) {
var content = await response.Content.ReadAsStringAsync ();
var items = JsonConvert.DeserializeObject(content);
if(items != null)
{
return items;
}
throw new Exception($"Service did not provide a response formatted as type: {typeof(T)}");
}else{
throw new Exception($"Service request failure. Code: {response.StatusCode}");
}
})
.ContinueWith(ex => { Debug.WriteLine ($"ERROR {ex.Message}");}, TaskContinuationOptions.OnlyOnFaulted));
}I just didn't want to confuse the poor guy with Task execution and moving between synchronous and asynchronous contexts when he doesn't even have generics down.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
-
I'm sorry Richard, that's not correct. Using the async keyword can mean that you want to return a Task that is not yet resolved, but doesn't necessarily mean that. It can simply mean that you're coalescing an asynchronous operation into a synchronous context, which is precisely what "await" does. Using the await keyword means that you want to use the value provided by Task.Result, not the Task itself. It blocks execution of the method (ie runs synchronously) until the awaited Task is completed. That means that while the method is flagged as async, it resolves async operations into a synchronous context. I understand that the terminology is a bit misleading and the documentation does not help. But the fact is that if you're blocking the thread, you are running synchronously. If the method were run asynchronously, it would look more like this:
public async Task RefreshData(string url)
{
return Task.Run(()=>
{
var response = await client.GetAsync (url);
if (response.IsSuccessStatusCode) {
var content = await response.Content.ReadAsStringAsync ();
var items = JsonConvert.DeserializeObject(content);
if(items != null)
{
return items;
}
throw new Exception($"Service did not provide a response formatted as type: {typeof(T)}");
}else{
throw new Exception($"Service request failure. Code: {response.StatusCode}");
}
})
.ContinueWith(ex => { Debug.WriteLine ($"ERROR {ex.Message}");}, TaskContinuationOptions.OnlyOnFaulted));
}I just didn't want to confuse the poor guy with Task execution and moving between synchronous and asynchronous contexts when he doesn't even have generics down.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
No, sorry, that's not correct. The
await client.GetAsync(url)
andawait response.Content.ReadAsStringAsync()
calls will not block the current thread. They will sign up the rest of the method as a continuation, which will be invoked when the returnedTask<T>
has completed. TheGetAsync
andReadAsStringAsync
calls (eventually) use an IO completion port to receive notification when the request has completed. That's the whole point ofasync
/await
. :) The only thing your example is doing differently is that it's using a background thread to run the very first part of the method - the call toGetAsync
. As a result, the rest of the method will not run on the captured execution context from the caller ofRefreshData
. But you could achieve the same result by adding.ConfigureAwait(false)
to the end of theGetAsync
andReadAsStringAsync
calls.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
No, sorry, that's not correct. The
await client.GetAsync(url)
andawait response.Content.ReadAsStringAsync()
calls will not block the current thread. They will sign up the rest of the method as a continuation, which will be invoked when the returnedTask<T>
has completed. TheGetAsync
andReadAsStringAsync
calls (eventually) use an IO completion port to receive notification when the request has completed. That's the whole point ofasync
/await
. :) The only thing your example is doing differently is that it's using a background thread to run the very first part of the method - the call toGetAsync
. As a result, the rest of the method will not run on the captured execution context from the caller ofRefreshData
. But you could achieve the same result by adding.ConfigureAwait(false)
to the end of theGetAsync
andReadAsStringAsync
calls.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
I was completely lazy with that sample and I accept that, but it was just to illustrate a point. I think the issue we're having is that we're discussing different contexts. I'm talking about the code that I'm looking at. I can't see the calling code, so am therefore not addressing it, and that's the code you're talking about. I realize that internal conventions are not the same as generally accepted practice, and my point about the return type and naming convention was not relevant, so I accept proper chastisement there. I think we can both agree that the code we're looking at, and just that code, will not behave in an asynchronous manner.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
-
I was completely lazy with that sample and I accept that, but it was just to illustrate a point. I think the issue we're having is that we're discussing different contexts. I'm talking about the code that I'm looking at. I can't see the calling code, so am therefore not addressing it, and that's the code you're talking about. I realize that internal conventions are not the same as generally accepted practice, and my point about the return type and naming convention was not relevant, so I accept proper chastisement there. I think we can both agree that the code we're looking at, and just that code, will not behave in an asynchronous manner.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
But that's the point - it WILL behave in an asynchronous manner! :)
public async Task<List<todoitem>> RefreshDataAsync ()
{
Items = new List<todoitem> ();
RestUrl = "http://localhost:9054/TotoItem"
var uri = new Uri (string.Format (RestUrl, string.Empty));try { // Runs synchronously up to here... var response = await client.GetAsync (uri); // The rest of the method is signed up as a continuation, // invoked when the IO completion port notifies that the request has been completed. // The current thread is NOT blocked. // Depending on the calling context, the continuation MIGHT run on the same thread as the caller. // But this is handled asynchronously; the thread does not block waiting to run the continuation. // This can be changed by adding ".ConfigureAwait(false)" after "GetAsync(url)". if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync (); // Again, the rest of the method is signed up as a continuation. // The thread is NOT blocked waiting for the response content to be downloaded. Items = JsonConvert.DeserializeObject <List<todoitem>> (content); // The JSON conversion IS run syncronously. // But async support in JSON.NET was only recently added (v10.1), so there's probably no option here. } } catch (Exception ex) { Debug.WriteLine (@" ERROR {0}", ex.Message); } return Items;
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
But that's the point - it WILL behave in an asynchronous manner! :)
public async Task<List<todoitem>> RefreshDataAsync ()
{
Items = new List<todoitem> ();
RestUrl = "http://localhost:9054/TotoItem"
var uri = new Uri (string.Format (RestUrl, string.Empty));try { // Runs synchronously up to here... var response = await client.GetAsync (uri); // The rest of the method is signed up as a continuation, // invoked when the IO completion port notifies that the request has been completed. // The current thread is NOT blocked. // Depending on the calling context, the continuation MIGHT run on the same thread as the caller. // But this is handled asynchronously; the thread does not block waiting to run the continuation. // This can be changed by adding ".ConfigureAwait(false)" after "GetAsync(url)". if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync (); // Again, the rest of the method is signed up as a continuation. // The thread is NOT blocked waiting for the response content to be downloaded. Items = JsonConvert.DeserializeObject <List<todoitem>> (content); // The JSON conversion IS run syncronously. // But async support in JSON.NET was only recently added (v10.1), so there's probably no option here. } } catch (Exception ex) { Debug.WriteLine (@" ERROR {0}", ex.Message); } return Items;
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
var response = await client.GetAsync (uri);
// Code execution within this method halted until response is resolved
if (response.IsSuccessStatusCode) {This is an synchronous pattern within the context of the method, which is the only code that we see. No other actions will be taken by the available code until this resolves. The lack of thread blocking is not the point, the code execution flow in the method is the same as if the thread were blocked. The Task continuation is only of interest to the calling code so that it can treat it appropriately.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
-
var response = await client.GetAsync (uri);
// Code execution within this method halted until response is resolved
if (response.IsSuccessStatusCode) {This is an synchronous pattern within the context of the method, which is the only code that we see. No other actions will be taken by the available code until this resolves. The lack of thread blocking is not the point, the code execution flow in the method is the same as if the thread were blocked. The Task continuation is only of interest to the calling code so that it can treat it appropriately.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli
The same argument applies to your "asynchronous" version:
public async Task<T> RefreshData<T>(string url)
{
return Task<T>.Run(()=>
{
var response = await client.GetAsync (url);
// Code execution within this method halted until response is resolved
...
})
.ContinueWith(ex => { Debug.WriteLine ($"ERROR {ex.Message}");}, TaskContinuationOptions.OnlyOnFaulted));
}You've just moved the "logically synchronous" part to a lambda method, at the expense of making the outer method more complicated. :) Adding the
*Async
suffix to a task-returning method is the recommended naming convention. Adding an extraTask.Run
for extra asynchronicity doesn't really achieve anything. It's only going to have any effect if your method has with a long-running synchronous block before the firstawait
call, andTask.Yield
would probably be a better solution for that.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
The same argument applies to your "asynchronous" version:
public async Task<T> RefreshData<T>(string url)
{
return Task<T>.Run(()=>
{
var response = await client.GetAsync (url);
// Code execution within this method halted until response is resolved
...
})
.ContinueWith(ex => { Debug.WriteLine ($"ERROR {ex.Message}");}, TaskContinuationOptions.OnlyOnFaulted));
}You've just moved the "logically synchronous" part to a lambda method, at the expense of making the outer method more complicated. :) Adding the
*Async
suffix to a task-returning method is the recommended naming convention. Adding an extraTask.Run
for extra asynchronicity doesn't really achieve anything. It's only going to have any effect if your method has with a long-running synchronous block before the firstawait
call, andTask.Yield
would probably be a better solution for that.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
As I said: it was a bad, rushed example. I will continue to accept bads on that.
"There are three kinds of lies: lies, damned lies and statistics." - Benjamin Disraeli