How to: Wrap EAP Patterns in a Task
.NET Framework 4.5
The following example shows how to expose an arbitrary sequence of Event-Based Asynchronous Pattern (EAP) operations as one task by using a TaskCompletionSource<TResult>. The example also shows how to use a CancellationToken to invoke the built-in cancellation methods on the WebClient objects.
class WebDataDownloader { static void Main() { WebDataDownloader downloader = new WebDataDownloader(); string[] addresses = { "http://www.msnbc.com", "http://www.yahoo.com", "http://www.nytimes.com", "http://www.washingtonpost.com", "http://www.latimes.com", "http://www.newsday.com" }; CancellationTokenSource cts = new CancellationTokenSource(); // Create a UI thread from which to cancel the entire operation Task.Factory.StartNew(() => { Console.WriteLine("Press c to cancel"); if (Console.ReadKey().KeyChar == 'c') cts.Cancel(); }); // Using a neutral search term that is sure to get some hits. Task<string[]> webTask = downloader.GetWordCounts(addresses, "the", cts.Token); // Do some other work here unless the method has already completed. if (!webTask.IsCompleted) { // Simulate some work. Thread.SpinWait(5000000); } string[] results = null; try { results = webTask.Result; } catch (AggregateException e) { foreach (var ex in e.InnerExceptions) { OperationCanceledException oce = ex as OperationCanceledException; if (oce != null) { if (oce.CancellationToken == cts.Token) { Console.WriteLine("Operation canceled by user."); } } else Console.WriteLine(ex.Message); } } if (results != null) { foreach (var item in results) Console.WriteLine(item); } Console.ReadKey(); } Task<string[]> GetWordCounts(string[] urls, string name, CancellationToken token) { TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>(); WebClient[] webClients = new WebClient[urls.Length]; // If the user cancels the CancellationToken, then we can use the // WebClient's ability to cancel its own async operations. token.Register(() => { foreach (var wc in webClients) { if (wc != null) wc.CancelAsync(); } }); object m_lock = new object(); int count = 0; List<string> results = new List<string>(); for (int i = 0; i < urls.Length; i++) { webClients[i] = new WebClient(); #region callback // Specify the callback for the DownloadStringCompleted // event that will be raised by this WebClient instance. webClients[i].DownloadStringCompleted += (obj, args) => { if (args.Cancelled == true) { tcs.TrySetCanceled(); return; } else if (args.Error != null) { // Pass through to the underlying Task // any exceptions thrown by the WebClient // during the asynchronous operation. tcs.TrySetException(args.Error); return; } else { // Split the string into an array of words, // then count the number of elements that match // the search term. string[] words = null; words = args.Result.Split(' '); string NAME = name.ToUpper(); int nameCount = (from word in words.AsParallel() where word.ToUpper().Contains(NAME) select word) .Count(); // Associate the results with the url, and add new string to the array that // the underlying Task object will return in its Result property. results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name)); } // If this is the last async operation to complete, // then set the Result property on the underlying Task. lock (m_lock) { count++; if (count == urls.Length) { tcs.TrySetResult(results.ToArray()); } } }; #endregion // Call DownloadStringAsync for each URL. Uri address = null; try { address = new Uri(urls[i]); // Pass the address, and also use it for the userToken // to identify the page when the delegate is invoked. webClients[i].DownloadStringAsync(address, address); } catch (UriFormatException ex) { // Abandon the entire operation if one url is malformed. // Other actions are possible here. tcs.TrySetException(ex); return tcs.Task; } } // Return the underlying Task. The client code // waits on the Result property, and handles exceptions // in the try-catch block there. return tcs.Task; }