在以下WPF
应用的MSDN示例中,演示了多个异步async/await
的{{1}}实现,Tasks
对象显然未被使用/需要,即异步执行Dispatcher
似乎可以直接访问UI控件(在本例中为Tasks
resultTextBox
控件 - 请参阅第TextBox
行。该应用已经过测试,表现如预期。
但是,问题仍然如果此实现能够正确处理可能的竞争条件,例如,如果等待和完成resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
尝试访问Task
控件,而后者仍处理以前完成的TextBox
更新?在实际意义上,是WPF Task
对象仍然需要在Dispatcher
多任务实现中处理这种潜在的并发/竞争条件问题(或者,可能是,联锁功能已经以某种方式隐式实现在这样的async / await编程构造中??
清单1 。 MSDN文章"启动多个异步任务并在完成时处理它们#34; (https://msdn.microsoft.com/en-us/library/jj155756.aspx)
async/await
注意 :我要感谢 Stephen Cleary 提供的出色答案和富有洞察力的解释,同时也希望强调推荐的改进在他的解决方案中概述,即:在原始MSDN示例中使用using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add a using directive and a reference for System.Net.Http.
using System.Net.Http;
// Add the following using directive.
using System.Threading;
namespace ProcessTasksAsTheyFinish
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
// Make a list of web addresses.
List<string> urlList = SetUpURLList();
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// ***Use ToList to execute the query and start the tasks.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
// Await the completed task.
int length = await firstFinishedTask;
resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
}
}
private List<string> SetUpURLList()
{
List<string> urls = new List<string>
{
"http://msdn.microsoft.com",
"http://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"http://msdn.microsoft.com/en-us/library/hh290136.aspx",
"http://msdn.microsoft.com/en-us/library/dd470362.aspx",
"http://msdn.microsoft.com/en-us/library/aa578028.aspx",
"http://msdn.microsoft.com/en-us/library/ms404677.aspx",
"http://msdn.microsoft.com/en-us/library/ff730837.aspx"
};
return urls;
}
async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
}
}
通过封装在单行代码中的相当紧凑的解决方案替换那个不必要/复杂的代码块,即:WhenAny
(顺便说一下,我正在使用这个许多实际应用程序中的替代方案,特别是处理多个股票网络查询的在线市场数据应用程序)。
非常感谢,斯蒂芬!
答案 0 :(得分:2)
但是,如果此实现能够正确处理可能的竞争条件,问题仍然存在,例如,如果等待和完成的任务尝试访问该TextBox控件,而后者仍在处理先前完成的任务的更新?
没有竞争条件。 UI线程一次只做一件事。
实际上,WPF Dispatcher对象仍然需要在async / await多任务实现中处理这种潜在的并发/竞争条件问题(或者,可能是,在诸如async / await编程构造中以某种方式隐式实现了联锁功能) ?
确实如此,但您不必明确使用它。正如我在async
intro中所描述的那样,await
关键字(默认情况下)将捕获当前上下文并继续在该上下文中执行async
方法。 “上下文”为SynchronizationContext.Current
(如果当前的SyncCtx为TaskScheduler.Current
,则为null
。
在这种情况下,它将捕获UI SynchronizationContext
,它使用封面下的WPF Dispatcher来调度UI线程上async
方法的剩余部分。
在旁注中,我不是“Task.WhenAny
列表的忠实粉丝,并且在完成”方法时从列表中删除。如果通过添加DownloadAndUpdateAsync
方法重构,我发现代码更清晰:
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
// Make a list of web addresses.
List<string> urlList = SetUpURLList();
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task> downloadTasksQuery =
from url in urlList select DownloadAndUpdateAsync(url, client, ct);
// ***Use ToList to execute the query and start the tasks.
List<Task> downloadTasks = downloadTasksQuery.ToList();
await Task.WhenAll(downloadTasks);
}
async Task DownloadAndUpdateAsync(string url, HttpClient client, CancellationToken ct)
{
var length = await ProcessURLAsync(url, client, ct);
resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
}
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}