In the Async CTP there is an extension method with the signature
WebClient.DownloadStringTaskAsync(Uri,CancellationToken)
Where is this in VS11?
Do I need to install the Async CTP to get this method?
In .NET 4.5, you would probably use the new HttpClient Class, in particular the GetStringAsync Method.
It's unfortunate that CancellationToken support isn't built in, but here's how you can approximate it by leveraging the Register and CancelAsync methods:
var downloadTask = webClient.DownloadStringTaskAsync(source);
string text;
using (cancellationToken.Register(() => webClient.CancelAsync()))
{
text = await downloadTask;
}
It's still there in .Net 4.5 beta, see MSDN, except it's not an extension method anymore.
What you may be referring to is the fact that WebClient is not included in .Net for Metro-style apps. There, you should probably use HttpClient. Another option is to use HttpWebRequest, which is still present and has been extended with Task-based async methods as well.
Both classes System.Net.WebClient and System.Net.Http.HttpClient have an async function. This enables you to create an async function. While the GetStringAsync function is running asynchronously you can check regularly if cancellation is requested.
Example:
using System.Net.Http;
class HttpSonnetFetcher
{
const string sonnetsShakespeare = #"http://www.gutenberg.org/cache/epub/1041/pg1041.txt";
public async Task<IEnumerable<string>> Fetch(CancellationToken token)
{
string bookShakespeareSonnets = null;
using (var downloader = new HttpClient())
{
var downloadTask = downloader.GetStringAsync(sonnetsShakespeare);
// wait until downloadTask finished, but regularly check if cancellation requested:
while (!downloadTask.Wait(TimeSpan.FromSeconds(0.2)))
{
token.ThrowIfCancellationRequested();
}
// if still here: downloadTask completed
bookShakespeareSonnets = downloadTask.Result;
}
// just for fun: find a nice sonnet, remove the beginning, split into lines and return 12 lines
var indexNiceSonnet = bookShakespeareSonnets.IndexOf("Shall I compare thee to a summer's day?");
return bookShakespeareSonnets.Remove(0, indexNiceSonnet)
.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Take(12);
}
}
Usage will be as follows:
private void TestCancellationHttpClient()
{
try
{
var sonnetFetcher = new HttpSonnetFetcher();
var cancellationTokenSource = new CancellationTokenSource();
var sonnetTask = Task.Run(() => sonnetFetcher.Fetch(cancellationTokenSource.Token));
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
// meanwhile do something else, checking regularly if the task finished, or if you have nothing to do, just Task.Wait():
while (!sonnetTask.Wait(TimeSpan.FromSeconds(0.25)))
{
Console.Write('.');
}
// if still here: the sonnet is fetched. return value is in sonnetTask.Result
Console.WriteLine("A nice sonnet by William Shakespeare:");
foreach (var line in sonnetTask.Result)
{
Console.WriteLine(line);
}
}
catch (OperationCanceledException exc)
{
Console.WriteLine("Canceled " + exc.Message);
}
catch (AggregateException exc)
{
Console.WriteLine("Task reports exceptions");
var x = exc.Flatten();
foreach (var innerException in x.InnerExceptions)
{
Console.WriteLine(innerException.Message);
}
}
catch (Exception exc)
{
Console.WriteLine("Exception: " + exc.Message);
}
}
Try this in a simple console program, and see that the sonnet is fetched properly, Decrease CancelAfter from 10 seconds until, say 0.1 second, and see that the task is properly cancelled.
Nota Bene: Although an OperationCancelledException is thrown, this exception is wrapped as an inner exception of an AggregateException. All exceptions that occur within a task are always wrapped in an AggregateException.
Related
I have a Regex which might lead to dead loop when dealing some files.
Although I should fix this problem sooner or later, to make it safe, I want to run it in a cancel-able tasks.
I've read some answers here, but none of the following solutions worked. Any help?
public static Task<CodeFile> CreateCodeFile(string fileName)
{
var fileInfo = new FileInfo(fileName);
foreach (var codeFileFactory in CodeFileFactories)
{
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(1000);
var task = new Task<CodeFile>(() => TryToCreateCodeFile(codeFileFactory, fileInfo),
cancellationTokenSource.Token);
task.RunSynchronously(); //Get lost here.
if (!task.Wait(1000))
cancellationTokenSource.Cancel();
File.WriteAllTextAsync("FailedLog.txt",
File.ReadAllTextAsync("FailedLog.txt") + "\r\n" + fileName);
cancellationTokenSource.Dispose();
return task;
}
return null;
}
or:
public static async Task<CodeFile> CreateCodeFile(string fileName)
{
var fileInfo = new FileInfo(fileName);
foreach (var codeFileFactory in CodeFileFactories)
{
var cancellationTokenSource = new CancellationTokenSource();
try
{
cancellationTokenSource.CancelAfter(1000);
var codeFile = await Task.Run(() => TryToCreateCodeFile(codeFileFactory, fileInfo),
cancellationTokenSource.Token); //Get lost here.
if (codeFile != null)
return codeFile;
}
catch (TaskCanceledException)
{
File.WriteAllTextAsync("FailedLog.txt",
File.ReadAllTextAsync("FailedLog.txt") + "\r\n" + fileName);
}
finally
{
cancellationTokenSource.Dispose();
}
}
return null;
}
And here is the function to be called:
private static CodeFile TryToCreateCodeFile(ICodeFileFactory codeFileFactory, FileInfo fileInfo)
{
var codeFile = codeFileFactory.CreateByName(fileInfo);
return codeFile;
//Somewhere deeper, .net Regex.Matches() caused the dead loop which I cannot control.
}
In this topic CancellationTokenSource.CancelAfter not working, I read something like "Your Sleep method is ignoring the CancellationToken." Does it happen in my case?
Cancellation tokens implement cooperative cancellation, which means the code being "cancelled" must cooperate, by actively checking the status of the token. Regex.Matches does not have overload which accepts the token, which means it can't be cancelled this way.
Tasks in general can't be magically cancelled. Code which task executes must do that explictly (with something like token.ThrowIfCancellationRequested()), your code doesn't do that so token doesn't do anything useful. Passing token to Task.Run is documented like this:
A cancellation token that can be used to cancel the work if it has not
yet started. Run(Action, CancellationToken) does not pass
cancellationToken to action.
So yes, if task is not started that will cancel it, not quite useful in your case.
If you perform some long running operation which does not accept and handle cancellation tokens (and you are not able to handle cancellation yourself, by checking token every now and then) - then you can't cancel it with them.
In your case I'd suggest Regex.MatchTimeout
The following sample code snippet implements the multitasking functionality by reading/processing the content of Web Url sample list List<string> urls utilizing async/await coding technique (similar to the MSDN demo sample: https://msdn.microsoft.com/en-us/library/jj155756.aspx). For testing purpose, the Url List<string> urls contains two erroneous items.
Listing 1. Error handling in WPF async/await implementation of multiple Tasks
namespace ProcessTasksAsTheyFinish
{
public partial class MainWindow : Window
{
CancellationTokenSource cts;
// sample Url list containing erroneous items
private List<string> SetUpURLList()
{
List<string> urls = new List<string>
{
"http://msdn.microsoft.com",
"http://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"error1",
"http://msdn.microsoft.com/en-us/library/aa578028.aspx",
"error2",
"http://msdn.microsoft.com/en-us/library/ms404677.aspx",
"http://msdn.microsoft.com/en-us/library/ff730837.aspx"
};
return urls;
}
public MainWindow() { InitializeComponent(); }
private async void startButton_Click(object sender, RoutedEventArgs e) {
resultsTextBox.Clear();
cts = new CancellationTokenSource();
try { await AccessTheWebAsync(cts.Token);}
finally { cts = null; }
}
private async Task AccessTheWebAsync(CancellationToken ct) {
try {
HttpClient client = new HttpClient();
// sample list of web addresses
List<string> urlList = SetUpURLList();
// query to create a collection of Tasks
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// run multiple Tasks in async mode
await Task.WhenAll(downloadTasksQuery.ToList());
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException){
resultsTextBox.Text += "\r\nDownloads canceled.";
}
catch (Exception ex){
resultsTextBox.Text += Environment.NewLine + ex.Message;
}
}
private async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
{
try
{
HttpResponseMessage response = await client.GetAsync(url, ct);
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
resultsTextBox.Text += String.Format("\r\nLength: {0}", urlContents.Length);
return urlContents.Length;
}
catch
{
//if (cts != null) cts.Cancel();
throw;
}
}
}
}
// sample output:
// Length: 196315
// Length: 468941
// Length: 158496
// Length: 200790
// Length: 48022
// An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.
The output contains length info calculated for the 5 valid Urls and error notification corresponding to the Exception ex.Message.
The goal was to modify the functionality in a such way that the first exception will completely terminate the multitasking execution of the await Task.WhenAll. In other words, to implement the sort of "All or Nothing" business logic pertinent to the awaited multiple Tasks.
In this example, it's been achieved by adding the following statement to the Exception handling block using CancellationToken (shown as commented line in Listing 1):
if (cts != null) cts.Cancel();
The output text looks as expected, corresponding to ex.Message:
An invalid request URI was provided. The request URI must either be an
absolute URI or BaseAddress must be set.
EDITED: I was looking for a simplified implementation of the same "All or Nothing" functionality (enabling to terminate the entire set of Tasks in the Task.WhenAll procedure on the first error) without using the CancellationToken. As per the insightful comments posted by #Stephen Cleary, there is no any overload "shortcut" option and no better solution (in terms of simplicity) than the current one.
There is no overload that provides this kind of functionality built-in.
In general, automatic cancelling of CancellationTokenSource objects is not in the BCL (with the exception of the timeout convenience methods, which automatically cancel based on a timer). If I were to hazard a guess, I'd say that the BCL team feels there's too many differing use cases for these kinds of overloads, so they wouldn't be useful enough to a broad enough audience to be built-in.
Instead, the appropriate solution is to trigger the CTS yourself once any one of the tasks faults, which is what your code is already doing.
I'm playing with a piece of code I wrote a while back. That piece of code deals with making a few requests in an async manner.
var client = new HttpClient();
var searchPromises = searchTerms
.Select(GetSearchUrl)
.Select(client.GetStringAsync);
var searchPages = await Task.WhenAll(searchPromises);
What happens is I create a new HttpClient. Using some search terch terms I compose search engine urls. Then I use those urls as inputs to get tasks representing the async requests for a page with the results. And last, I await those responses using Task.WhenAll to group them together.
The problem is if just one of those requests gets a 404, a 500 or anything like that my code throws an AggregateException.
Is there a way of specifying what should happen in the case of an error in one of those threads, so that I get a result from everything else?
I've looked at ContinueWith, but it doesn't seem to fit the bill, that is, it doesn't know how to deal with all the errors, just the aggregate one.
What happens is I create a new HttpClient. Using some search terch terms I compose search engine urls. Then I use those urls as inputs to get tasks representing the async requests for a page with the results. And last, I await those responses using Task.WhenAll to group them together.
Is there a way of specifying what should happen in the case of an error in one of those threads, so that I get a result from everything else?
IMO, the easiest solution is to change how you think about the problem. Right now, you're thinking "perform a download on each url" and then "what for them all to complete and handle errors on a per-item basis". Just change your operation ("download") to include anything you want to do per-item. In other words, what you want to do is "perform a download on each url and handle errors" and then "wait for them all to complete":
var client = new HttpClient();
var searchPromises = searchTerms
.Select(GetSearchUrl)
.Select(url => DownloadAsync(client, url));
var searchPages = await Task.WhenAll(searchPromises);
var successfulSearchPages = searchPages.Where(x => x != null);
...
private static async Task<string> DownloadAsync(HttpClient client, string url)
{
try
{
return await client.GetStringAsync(url);
}
catch (HttpRequestException ex)
{
// TODO: Perform appropriate error handling
return null;
}
}
Task.WhenAll will return a task that is completed when all the tasks passed as argument are completed.
If any of the tasks passed as argument ends in a Faulted state (an exception was thrown), the returned task will also end in a Faulted state and its Exception property will contain the aggregation of all exceptions thrown by the tasks passed as argument.
Because the code generated by the compiler picks the first exceptin on the list, only the excpetion thrown by the first exception that throws (not the first exception thrwing) will be rethrown.
But the tasks passed as argument still exist and can still be queried for result.
This code snippet shows this working:
var tasks = new Task[] {
((Func<Task>)(async () =>
{
await Task.Delay(10);
await Task.Delay(10);
await Task.Delay(10);
throw new Exception("First");
}))(),
((Func<Task>)(async () =>
{
await Task.Delay(10);
throw new Exception("Second");
}))(),
((Func<Task>)(async () =>
{
await Task.Delay(10);
}))()
};
var allTasks = Task.WhenAll(tasks);
try
{
await allTasks;
}
catch (Exception ex)
{
Console.WriteLine("Overall failed: {0}", ex.Message);
}
for(var i = 0; i < tasks.Length; i++)
{
try
{
await tasks[i];
Console.WriteLine("Taks {0} succeeded!", i);
}
catch (Exception ex)
{
Console.WriteLine("Taks {0} failed!", i);
}
}
/*
Overall failed: First
Taks 0 failed!
Taks 1 failed!
Taks 2 succeeded!
*/
You can create your own version of Task.WhenAll that returns just the results disregarding any exception using Task.WhenAny:
public static async Task<IEnumerable<TResult>> WhenAllSwallowExceptions<TResult>(IEnumerable<Task<TResult>> tasks)
{
var tasklist = tasks.ToList();
var results = new List<TResult>();
while (tasklist.Any())
{
var completedTask = await Task.WhenAny(tasklist);
try
{
results.Add(await completedTask);
}
catch (Exception e)
{
// handle
}
tasklist.Remove(completedTask);
}
return results;
}
Usage:
var searchPages = await WhenAllSwallowExceptions(searchPromises);
This waits for tasks one at a time (with Task.WhenAny) and aggregates all the results (if there are any).
I've found a way to do this, after many iterations. Tasks are starting to look like things that you need a library to abstract.
Anyway, here's the code:
var client = new HttpClient();
var exceptions = new ConcurrentBag<Exception>();
var searchPromises = searchTerms
.Select(GetSearchUrl)
.Select(client.GetStringAsync)
.Select(t=>t.Catch(e=>exceptions.Add(e)));
var searchPages = (await Task.WhenAll(searchPromises))
.Where(r => r != null);
And the implementation for Catch:
public static Task<TResult> Catch<TResult>(this Task<TResult> self, Action<Exception> exceptionHandlerTask)
{
return self.ContinueWith(s =>
{
if (!s.IsFaulted)
{
return s.Result;
}
exceptionHandlerTask(s.Exception);
return default(TResult);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.DenyChildAttach,
TaskScheduler.Default);
}
What happens now is that it gives you a way to append a failure state function to the Task<T> promise. This allows me to still have chainability. It is a shame that c# doesn't have robust support for functional pattern matching to make this easier.
Edit: added minimal code for error logging.
Edit: separated the code for logging errors to be more generic/reusable.
Edit: separated the code for saving the errors from the Catch function.
I want to consume an Web API and I see many people recommending System.Net.Http.HttpClient.
That's fine... but I have only VS-2010, so I cannot use async/await just yet. Instead, I guess I could use Task<TResult> in combination to ContinueWith. So I tried this piece of code:
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.GetStringAsync(STR_URL_SERVER_API_USERS).ContinueWith(task =>
{
var usersResultString = task.Result;
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(usersResultString);
});
My first observation was to realize that it doesn't generate any error if URL is not available, but maybe there will be more errors like this...
So I am trying to find a way to handle exceptions for such async calls (particularly for HttpClient). I noticed that "Task" has IsFaulted property and an AggregateException which maybe could be used, but I am not sure yet how.
Another observation was that GetStringAsync returns Task<string>, but GetAsync returns Task<HttpResponseMessage>. The latter could be maybe more useful, since it presents a StatusCode.
Could you share a pattern on how to use the async calls correctly and handle exceptions in a good way? Basic explanation would be appreciated as well.
I would not use a separate ContinueWith continuation for successful and faulted scenarios. I'd rather handle both cases in a single place, using try/catch:
task.ContinueWith(t =>
{
try
{
// this would re-throw an exception from task, if any
var result = t.Result;
// process result
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
lbUsers.Clear();
lbUsers.Items.Add("Error loading users!");
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()
);
If t is a non-generic Task (rather than a Task<TResult>), you can do t.GetAwaiter().GetResult() to re-throw the original exception inside the ContinueWith lambda; t.Wait() would work too. Be prepared to handle AggregatedException, you can get to the inner exception with something like this:
catch (Exception ex)
{
while (ex is AggregatedException && ex.InnerException != null)
ex = ex.InnerException;
MessageBox.Show(ex.Message);
}
If you're dealing with a series of ContinueWith, usually you don't have to handle exceptions inside each ContinueWith. Do it once for the outermost resulting task, e.g.:
void GetThreePagesV1()
{
var httpClient = new HttpClient();
var finalTask = httpClient.GetStringAsync("http://example.com")
.ContinueWith((task1) =>
{
var page1 = task1.Result;
return httpClient.GetStringAsync("http://example.net")
.ContinueWith((task2) =>
{
var page2 = task2.Result;
return httpClient.GetStringAsync("http://example.org")
.ContinueWith((task3) =>
{
var page3 = task3.Result;
return page1 + page2 + page3;
}, TaskContinuationOptions.ExecuteSynchronously);
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap()
.ContinueWith((resultTask) =>
{
httpClient.Dispose();
string result = resultTask.Result;
try
{
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
Any exceptions thrown inside inner tasks will propagate to the outermost ContinueWith lambda as you're accessing the results of the inner tasks (taskN.Result).
This code is functional, but it's also ugly and non-readable. JavaScript developers call it The Callback Pyramid of Doom. They have Promises to deal with it. C# developers have async/await, which you're unfortunately not able to use because of the VS2010 restrain.
IMO, the closest thing to the JavaScript Promises in TPL is Stephen Toub's Then pattern. And the closest thing to async/await in C# 4.0 is his Iterate pattern from the same blog post, which uses the C# yield feature.
Using the Iterate pattern, the above code could be rewritten in a more readable way. Note that inside GetThreePagesHelper you can use all the familiar synchronous code statements like using, for, while, try/catch etc. It is however important to understand the asynchronous code flow of this pattern:
void GetThreePagesV2()
{
Iterate(GetThreePagesHelper()).ContinueWith((iteratorTask) =>
{
try
{
var lastTask = (Task<string>)iteratorTask.Result;
var result = lastTask.Result;
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
IEnumerable<Task> GetThreePagesHelper()
{
// now you can use "foreach", "using" etc
using (var httpClient = new HttpClient())
{
var task1 = httpClient.GetStringAsync("http://example.com");
yield return task1;
var page1 = task1.Result;
var task2 = httpClient.GetStringAsync("http://example.net");
yield return task2;
var page2 = task2.Result;
var task3 = httpClient.GetStringAsync("http://example.org");
yield return task3;
var page3 = task3.Result;
yield return Task.Delay(1000);
var resultTcs = new TaskCompletionSource<string>();
resultTcs.SetResult(page1 + page1 + page3);
yield return resultTcs.Task;
}
}
/// <summary>
/// A slightly modified version of Iterate from
/// http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
/// </summary>
public static Task<Task> Iterate(IEnumerable<Task> asyncIterator)
{
if (asyncIterator == null)
throw new ArgumentNullException("asyncIterator");
var enumerator = asyncIterator.GetEnumerator();
if (enumerator == null)
throw new InvalidOperationException("asyncIterator.GetEnumerator");
var tcs = new TaskCompletionSource<Task>();
Action<Task> nextStep = null;
nextStep = (previousTask) =>
{
if (previousTask != null && previousTask.Exception != null)
tcs.SetException(previousTask.Exception);
if (enumerator.MoveNext())
{
enumerator.Current.ContinueWith(nextStep,
TaskContinuationOptions.ExecuteSynchronously);
}
else
{
tcs.SetResult(previousTask);
}
};
nextStep(null);
return tcs.Task;
}
I am struggling around the (seems so) pretty famous problem of the exception handling by using the async/await pattern. Specifically my context is on a HTTP client, but I have also tried with a much simpler test, and it behaves the same.
Consider the below program, which is a super-simplified version of my original app's context.
class Program
{
static void Main(string[] args)
{
Test();
Console.Write("Press any key...");
Console.ReadKey();
Console.WriteLine();
}
static async void Test()
{
var c = new MyClient();
try
{
var uri = new Uri("http://www.google.com/"); //valid address
var s = await c.GetString(uri);
Console.WriteLine(s.Length);
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
}
try
{
var uri = new Uri("http://www.foo.bah/"); //non-existent address
var s = await c.GetString(uri);
Console.WriteLine(s.Length);
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
}
}
}
class MyClient
{
public async Task<string> GetString(Uri uri)
{
var client = new HttpClient();
return await client.GetStringAsync(uri);
}
}
When the program starts, it downloads the first web site's page as a string, then displays its length: that's fine. Afterward, when the same operation is performed against an invalid address, the client raises a WebException (that's what I want), but it's not caught.
UPDATE: as "not caught", I mean that the code actually does not flow through the "catch" branch and silently displays the exception message. Instead, the exception is shown by the VS IDE, and the debugging breaks.
Any decent solution to catch the exception?
Many thanks in advance.
Although you have already figured out the exception is HttpRequestException not WebException, still I would like to highlight few important things about async-await operator usage.
async void is of type fire & forget and is only & only for event handlers.
As soon as compiler reaches first await operator inside async method control returns to the caller.
Debugging your code :-
Since you are using async void in Test method so the control returns to the caller and execution continues to line Console.Write("Press any key..."); without having any information about the Task and then you are waiting for the user input.
In the meanwhile response from awaited method comes and the execution continues inside Test method.
If you comment out the line Console.ReadKey(); inside main() OR user provides input immediately then you'll notice that response may or may not get printed. This is because you are not waiting on the Task getting executed you simply trusted on the user that he will not enter anything till your Task completes.
Solution:-
Solution is to return Task from Test() and then wait till it finishes, below is the updated code also note adding Async at the end of method name is the naming convention you must follow to save you from the headache of distinguishing between asynchronous and synchronous methods.
class Program
{
static void Main(string[] args)
{
Task task = TestAsync();
Console.Write("Press any key...");
task.wait();
//Console.ReadKey();
Console.WriteLine();
}
static async Task<string> TestAsync()
{
var c = new MyClient();
try
{
var uri = new Uri("http://www.google.com/"); //valid address
var s = await c.GetStringAsync(uri);
Console.WriteLine(s.Length);
}
catch (HttpRequestException ex)
{
Console.WriteLine(ex.Message);
}
try
{
var uri = new Uri("http://www.foo.bah/"); //non-existent address
var s = await c.GetStringAsync(uri);
Console.WriteLine(s.Length);
}
catch (HttpRequestException ex)
{
Console.WriteLine(ex.Message);
}
//to avoid compiler error
return null;
}
}
class MyClient
{
public async Task<string> GetStringAsync(Uri uri)
{
var client = new HttpClient();
return await client.GetStringAsync(uri);
}
}