调度员在WPF应用程序实现多个异步任务 [英] Dispatcher in WPF apps implementing multiple async Tasks

查看:474
本文介绍了调度员在WPF应用程序实现多个异步任务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

WPF 应用程序,它演示了以下MSDN例如异步/的await 执行多个异步的任务调度对象显然是不使用/需要,即异步执行任务似乎已经到UI控件直接访问(在这种情况下, resultTextBox 文本框控制 - 看行 resultsTextBox.Text + =的String.Format(下载的\\ r \\ nLength:{0},长度); )。该应用程序已经过测试,达到预期效果。

然而,在问题仍然如果这个实现是能够妥善处理可能的竞争条件,对于例如,如果期待已久,并已完成任务试图访问而后者仍在处理从previously完成任务<更新的文本框控制/ code>?在实际意义上的是WPF 调度对象仍然需要处理这种潜在的并发性/竞争条件问题异步/的await 执行多任务(或者,也可以,互锁功能已经在某种程度上隐含在这样的实现异步/等待编程结构)?

清单1 即可。 MSDN文章启动多个异步任务和处理它们,因为他们完成( https://开头msdn.microsoft.com/en-us/library/jj155756.aspx

 使用系统;
使用System.Collections.Generic;
使用System.Linq的;
使用System.Text;
使用System.Threading.Tasks;
使用System.Windows;
使用System.Windows.Controls的;
使用System.Windows.Data;
使用System.Windows.Documents;
使用System.Windows.Input;
使用System.Windows.Media;
使用System.Windows.Media.Imaging;
使用System.Windows.Navigation;
使用System.Windows.Shapes;//添加using指令和System.Net.Http参考。
使用System.Net.Http;//添加使用指令以下。
使用的System.Threading;
命名空间ProcessTasksAsTheyFinish
{
    公共部分类主窗口:窗口
    {
        //声明一个System.Threading.CancellationTokenSource。
        CancellationTokenSource CTS;        公共主窗口()
        {
            的InitializeComponent();
        }        私人异步无效startButton_Click(对象发件人,RoutedEventArgs E)
        {
            resultsTextBox.Clear();            //实例化CancellationTokenSource。
            CTS =新CancellationTokenSource();            尝试
            {
                等待AccessTheWebAsync(cts.Token);
                resultsTextBox.Text + =\\ r \\ nDownloads完整的。
            }
            赶上(OperationCanceledException)
            {
                resultsTextBox.Text + =\\ r \\ nDownloads取消\\ r \\ n;
            }
            赶上(例外)
            {
                resultsTextBox.Text + =\\ r \\ nDownloads失败\\ r \\ n;
            }            CTS = NULL;
        }
        私人无效cancelButton_Click(对象发件人,RoutedEventArgs E)
        {
            如果(CTS!= NULL)
            {
                cts.Cancel();
            }
        }
        异步任务AccessTheWebAsync(CT的CancellationToken)
        {
            HttpClient的客户端=新的HttpClient();            //使Web地址的列表。
            清单&LT;串GT; urlList = SetUpURLList();            // ***创建查询,在执行时,返回的任务集合。
            IEnumerable的&LT;任务&LT; INT&GT;&GT; downloadTasksQuery =
                从urlList URL选择ProcessURL(URL,客户端,CT);            // ***用了ToList执行查询并启动任务。
            清单&LT;任务&LT; INT&GT;&GT; downloadTasks = downloadTasksQuery.ToList();            // ***添加一个循环来处理的任务之一在一个时间,直到没有剩余。
            而(downloadTasks.Count大于0)
            {
                    //标识完成了第一个任务。
                    任务&LT; INT&GT; firstFinishedTask =等待Task.WhenAny(downloadTasks);                    // ***从列表中删除所选的任务,这样你不
                    //过程中,它超过一次。
                    downloadTasks.Remove(firstFinishedTask);                    //等待完成的任务。
                    INT长度=等待firstFinishedTask;
                    resultsTextBox.Text + =的String.Format(下载的\\ r \\ nLength:{0},长度);
            }
        }
        私人列表&LT;串GT; SetUpURLList()
        {
            清单&LT;串GT;网址=新的List&LT;串GT;
            {
                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
            };
            返回的网址;
        }
        异步任务&LT; INT&GT; ProcessURL(字符串URL,HttpClient的客户端的CancellationToken CT)
        {
            // GetAsync返回任务&LT; Htt的presponseMessage取代。
            HTT presponseMessage响应=等待client.GetAsync(URL,CT);            //检索来自Htt的presponseMessage的网站内容。
            字节[] = urlContents等待response.Content.ReadAsByteArrayAsync();            返回urlContents.Length;
        }
    }
}

注意:我想感谢的斯蒂芬·克利作为他出色的答案,而有见地的解释,也希望凸显建议改进在他的解决方案概述,即:取代,在利用原有MSDN例如不必要/复杂code座 WhenAny 由封装在code单行相当紧凑的解决方案,即:等待Task.WhenAll(downloadTasks); (顺便说一句,我是用在许多实际应用这种替代,尤其是在线市场数据的应用程序处理瓦特/多支股票网查询)。
非常感谢,斯蒂芬!


解决方案

  

然而,问题仍然存在,如果这个实现是能够妥善处理可能的竞争条件,例如,如果期待已久的完成任务尝试,而后者仍在处理从$ P $更新pviously访问TextBox控件完成任务?


有没有竞争状态。在UI线程只做一件事的时间。


  

在实际意义上说,仍然是需要处理异步这个潜在的并发性/竞争条件问题WPF调度对象/等待多任务执行(或者可能是,互锁功能已经在这样被莫名其妙地含蓄地实现异步/等待编程结构) ?


有,但你没有明确地使用它。正如我形容我的 异步介绍,在等待关键字(默认)将捕获当前的背景下,恢复执行异步法在这方面。在背景是 SynchronizationContext.Current (或 TaskScheduler.Current 如果当前SyncCtx是)。

在这种情况下,将捕获一个UI 的SynchronizationContext ,它使用WPF调度的幕后调度的异步在UI线程的方法。

在一个侧面说明,我不是一个大风扇的 Task.WhenAny 列表,从列表中删除,因为他们完成的做法。我发现code是,如果你重构通过添加 DownloadAndUpdateAsync 方法更简洁的:

 异步任务AccessTheWebAsync(CT的CancellationToken)
{
  HttpClient的客户端=新的HttpClient();  //使Web地址的列表。
  清单&LT;串GT; urlList = SetUpURLList();  // ***创建查询,在执行时,返回的任务集合。
  IEnumerable的&LT;任务&GT; downloadTasksQuery =
        从urlList URL选择DownloadAndUpdateAsync(URL,客户端,CT);  // ***用了ToList执行查询并启动任务。
  清单&LT;任务&GT; downloadTasks = downloadTasksQuery.ToList();  等待Task.WhenAll(downloadTasks);
}异步任务DownloadAndUpdateAsync(字符串URL,HttpClient的客户端的CancellationToken CT)
{
  VAR长度=等待ProcessURLAsync(URL,客户端,CT);
  resultsTextBox.Text + =的String.Format(下载的\\ r \\ nLength:{0},长度);
}异步任务&LT; INT&GT; ProcessURLAsync(字符串URL,HttpClient的客户端的CancellationToken CT)
{
  // GetAsync返回任务&LT; Htt的presponseMessage取代。
  HTT presponseMessage响应=等待client.GetAsync(URL,CT);  //检索来自Htt的presponseMessage的网站内容。
  字节[] = urlContents等待response.Content.ReadAsByteArrayAsync();  返回urlContents.Length;
}

In the following MSDN example of the WPF app, which demonstrates async/await implementation of the multiple async Tasks, the Dispatcher object apparently is not used/needed, i.e. asynchronously executed Tasks seem to have direct access to the UI controls (in this case resultTextBox TextBox control - see the line resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);). The app has been tested, performing as expected.

However, the question still remains if this implementation is capable of proper handling the possible race condition, for e.g., if the awaited and completed Task tries to access that TextBox control while the latter is still processing the update from previously completed Task? In practical sense, is WPF Dispatcher object still required to handle this potential concurrency/race condition issues in async/await multitasking implementation (or, may be, the interlocking functionality has been somehow implicitly implemented in such async/await programming construct)?

Listing 1. MSDN article "Start Multiple Async Tasks and Process Them As They Complete" (https://msdn.microsoft.com/en-us/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;
        }
    }
}

Note: I would like to thank Stephen Cleary for his excellent answer and rather insightful explanation, and also want to highlight the recommended improvement outlined in his solution, namely: replacing that unnecessary/complex code block in original MSDN example utilizing WhenAny by rather compact solution encapsulated in a single line of code, namely: await Task.WhenAll(downloadTasks); (btw, I was using this alternative in many practical apps, in particular, online market data app dealing w/multiple stocks web queries). Many thanks, Stephen!

解决方案

However, the question still remains if this implementation is capable of proper handling the possible race condition, for e.g., if the awaited and completed Task tries to access that TextBox control while the latter is still processing the update from previously completed Task?

There is no race condition. The UI thread only does one thing at a time.

In practical sense, is WPF Dispatcher object still required to handle this potential concurrency/race condition issues in async/await multitasking implementation (or, may be, the interlocking functionality has been somehow implicitly implemented in such async/await programming construct)?

It is, but you don't have to use it explicitly. As I describe in my async intro, the await keyword (by default) will capture the current context and resume executing the async method in that context. The "context" is SynchronizationContext.Current (or TaskScheduler.Current if the current SyncCtx is null).

In this case, it will capture a UI SynchronizationContext, which uses the WPF Dispatcher under the covers to schedule the remainder of the async method on the UI thread.

On a side note, I'm not a big fan of the "Task.WhenAny the list and remove from the list as they complete" approach. I find the code is much cleaner if you refactor by adding a DownloadAndUpdateAsync method:

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

这篇关于调度员在WPF应用程序实现多个异步任务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆