实现多个异步任务的WPF应用程序中的调度程序

时间:2016-04-07 16:08:28

标签: c# wpf async-await task-parallel-library dispatcher

在以下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(顺便说一下,我正在使用这个许多实际应用程序中的替代方案,特别是处理多个股票网络查询的在线市场数据应用程序)。 非常感谢,斯蒂芬!

1 个答案:

答案 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;
}