繁体   English   中英

WPF应用中的分派器,实现了多个异步任务

[英]Dispatcher in WPF apps implementing multiple async Tasks

在以下WPF应用程序的MSDN示例中,该示例演示了多个异步Tasks的异步执行async/await实现,显然不使用/不需要Dispatcher对象,即异步执行的Tasks似乎可以直接访问UI控件(在这种情况下, resultTextBox TextBox控件-参见行resultsTextBox.Text += String.Format("\\r\\nLength of the download: {0}", length); )。 该应用程序已经过测试,性能符合预期。

但是, 如果此实现是否能够正确处理可能的争用条件仍然存在问题 ,例如,等待并完成的Task尝试访问该TextBox控件,而后者仍在处理先前完成的Task的更新? 实际上, 是否仍需要WPF Dispatcher对象来处理async/await多任务实现中的这种潜在的并发/争用条件问题 (或者可能已经在这种异步/等待编程结构中隐式实现了互锁功能)?

清单1 MSDN文章“启动多个异步任务并在完成时处理它们”( https://msdn.microsoft.com/zh-cn/library/jj155756.aspx

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

注意 :我要感谢Stephen Cleary的出色回答和颇有见地的解释,并且还想强调他的解决方案中概述的建议改进,即:用紧凑的解决方案WhenAny使用WhenAny的原始MSDN示例中的不必要/复杂的代码块封装在一行代码中,即: await Task.WhenAll(downloadTasks); (顺便说一句,我在许多实际应用中都使用了这种替代方法,特别是在处理带有多个股票网络查询的在线市场数据应用中)。 非常感谢,斯蒂芬!

但是,仍然存在问题,即此实现是否能够正确处理可能的争用条件,例如,等待并完成的Task是否尝试访问该TextBox控件,而后者仍在处理来自先前完成的Task的更新?

没有比赛条件。 UI线程一次只做一件事。

实际上,是否仍需要WPF Dispatcher对象来处理异步/等待多任务实现中的这种潜在的并发/争用条件问题(或者可能已经在这种异步/等待编程结构中隐式实现了互锁功能)?

是的,但是您不必显式使用它。 正如我在async介绍中所述, await关键字(默认情况下)将捕获当前上下文并在该上下文中继续执行async方法。 “上下文”为SynchronizationContext.Current (如果当前SyncCtx为null则为TaskScheduler.Current )。

在这种情况下,它将捕获UI SynchronizationContext ,该UI会在幕后使用WPF Dispatcher来调度UI线程上的其余async方法。

附带说明一下,我不是“ Task.WhenAny列表并在列表完成Task.WhenAny其删除”的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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM