如何:在任务中包装 EAP 模式
下面的示例演示如何通过使用 TaskCompletionSource<TResult> 将任意基于事件的异步模式 (EAP) 操作序列公开为一个任务。 此示例还展示了如何使用 CancellationToken 对 WebClient 对象调用内置的取消方法。
示例
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);
}
}
}
finally
{
cts.Dispose();
}
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;
}
Class WebDataDownLoader
Dim tcs As New TaskCompletionSource(Of String())
Dim nameToSearch As String
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As Object
Dim count As Integer
Dim addresses() As String
Shared Sub Main()
Dim downloader As New WebDataDownLoader()
downloader.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"}
Dim cts As New CancellationTokenSource()
' Create a UI thread from which to cancel the entire operation
Task.Factory.StartNew(Sub()
Console.WriteLine("Press c to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
End Sub)
' Using a neutral search term that is sure to get some hits on English web sites.
' Please substitute your favorite search term.
downloader.nameToSearch = "the"
Dim webTask As Task(Of String()) = downloader.GetWordCounts(downloader.addresses, downloader.nameToSearch, cts.Token)
' Do some other work here unless the method has already completed.
If (webTask.IsCompleted = False) Then
' Simulate some work
Thread.SpinWait(5000000)
End If
Dim results As String() = Nothing
Try
results = webTask.Result
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
If (TypeOf (ex) Is OperationCanceledException) Then
Dim oce As OperationCanceledException = CType(ex, OperationCanceledException)
If oce.CancellationToken = cts.Token Then
Console.WriteLine("Operation canceled by user.")
End If
Else
Console.WriteLine(ex.Message)
End If
Next
Finally
cts.Dispose()
End Try
If (Not results Is Nothing) Then
For Each item As String In results
Console.WriteLine(item)
Next
End If
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
Public Function GetWordCounts(ByVal urls() As String, ByVal str As String, ByVal token As CancellationToken) As Task(Of String())
Dim webClients() As WebClient
ReDim webClients(urls.Length)
m_lock = New Object()
' If the user cancels the CancellationToken, then we can use the
' WebClient's ability to cancel its own async operations.
token.Register(Sub()
For Each wc As WebClient In webClients
If Not wc Is Nothing Then
wc.CancelAsync()
End If
Next
End Sub)
For i As Integer = 0 To urls.Length - 1
webClients(i) = New WebClient()
' Specify the callback for the DownloadStringCompleted
' event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler
Dim address As Uri = Nothing
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 ex As UriFormatException
tcs.TrySetException(ex)
Return tcs.Task
End Try
Next
' Return the underlying Task. The client code
' waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function
Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)
If args.Cancelled = True Then
tcs.TrySetCanceled()
Return
ElseIf Not args.Error Is Nothing Then
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.
Dim words() As String = args.Result.Split(" "c)
Dim NAME As String = nameToSearch.ToUpper()
Dim 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, nameToSearch))
End If
SyncLock (m_lock)
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End Sub