【问题标题】:Running two tasks in parallel with GUI update与 GUI 更新并行运行两个任务
【发布时间】:2019-03-11 09:39:54
【问题描述】:

我已经下载了 C# 中 async/await 的示例代码

https://code.msdn.microsoft.com/Async-Sample-Example-from-9b9f505c

现在我尝试对其进行调整以实现不同的目标:我想在执行GetStringAsync 的同时更新 GUI

所以这就是我所做的并且它有效,但我对我的代码有些怀疑。如果它是正确的或“正确”的方式来做到这一点。

1- 使用Task.WhenAll 并行运行两个Task

2- 任务方法 UpdateUIAsync 每 200 毫秒附加一个点到等待文本是否应该使用 dispatcher.begininvoke 完成,或者这样可以吗?

3- 分享使用字段finished 来同步行为,再次,“好的”还是有更好的方法?

public partial class MainWindow : Window
{
    // Mark the event handler with async so you can use await in it.
    private async void StartButton_Click(object sender, RoutedEventArgs e)
    {
        // Call and await separately.
        //Task<int> getLengthTask = AccessTheWebAsync();
        //// You can do independent work here.
        //int contentLength = await getLengthTask;
        finished = false;
        int[] contentLength = await Task.WhenAll(AccessTheWebAsync(), UpdateUIAsync());

        resultsTextBox.Text +=
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength[0]);
    }

    bool finished = false;

    // Three things to note in the signature:
    //  - The method has an async modifier. 
    //  - The return type is Task or Task<T>. (See "Return Types" section.)
    //    Here, it is Task<int> because the return statement returns an integer.
    //  - The method name ends in "Async."

    async Task<int> UpdateUIAsync()
    {
        resultsTextBox.Text = "Working ";
        while (!finished)
        {
            resultsTextBox.Text += ".";
            await Task.Delay(200);
        }
        resultsTextBox.Text += "\r\n";

        //Task<int> write = new Task<int>(() =>
        //{
        //    Dispatcher.BeginInvoke((Action)(() =>
        //    {
        //        resultsTextBox.Text = "Working ";
        //        while (!finished)
        //        {
        //            resultsTextBox.Text += ".";
        //            Task.Delay(200);
        //        }
        //        resultsTextBox.Text += "\r\n";
        //    }));

        //    return 1;
        //});

        return 1;
    }
    async Task<int> AccessTheWebAsync()
    {
        // You need to add a reference to System.Net.Http to declare client.
        HttpClient client = new HttpClient();

        // GetStringAsync returns a Task<string>. That means that when you await the
        // task you'll get a string (urlContents).
        Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

        // The await operator suspends AccessTheWebAsync.
        //  - AccessTheWebAsync can't continue until getStringTask is complete.
        //  - Meanwhile, control returns to the caller of AccessTheWebAsync.
        //  - Control resumes here when getStringTask is complete. 
        //  - The await operator then retrieves the string result from getStringTask.
        string urlContents = await getStringTask;
        finished = true;
        // The return statement specifies an integer result.
        // Any methods that are awaiting AccessTheWebAsync retrieve the length value.
        return urlContents.Length;
    }
}

【问题讨论】:

  • 对于第三个问题,请查看 ManualResetEvent Class docs.microsoft.com/en-us/dotnet/api/… 或其他线程同步助手,如 Semaphore、EventWaitHandle 等。
  • @Max - 为什么?此示例中没有多线程...因为所有代码都在 UI 线程上运行。
  • @AlexeiLevenkov,嗯...我不太确定。在引擎盖下,它使用Task.Run,它(根据其描述)“将指定的工作排队以在线程池上运行......”。我同意,Task.Run 方法倾向于重用当前线程,而不是提供真正的多任务处理,但是,如果应用程序已经在使用大量任务/线程等,它可能会改变。
  • @Max “它使用Task.Run”中的“它”是什么?帖子中的代码和 MSDN 示例中的代码都没有直接使用 Task.RunTask.DelayHttpClient.GetStringAsync 都没有创建线程(同样不使用 Task.Run)...
  • @Max 很高兴知道......但这不应该(假设 HttpClient 被合理实现)从外部观察 - 帖子中的所有代码都将在同一个线程上运行,而 HttpClient 在另一个线程上执行的任何操作不应访问共享数据。

标签: c# async-await dispatcher


【解决方案1】:

1- 使用Task.WhenAll并行运行两个Task

这些操作同时运行Task.WhenAll 是异步并发的适当机制。您的所有代码仅在一个线程上运行,因此这里没有真正的并行性。

2- 每 200 毫秒向等待文本附加一个点的任务方法 UpdateUIAsync 是否应该使用 dispatcher.begininvoke 完成,或者这样可以吗?

Dispatcher 不是必需的,因为代码在 UI 线程上运行。不过,我确实推荐 IProgress&lt;T&gt; 模式 as Paulo recommended,因为这有助于使您的代码更易于测试,并且与特定 UI 的关联更少。

3- 再次共享使用完成的字段来同步行为,“好的”还是有更好的方法?

在这种情况下,不受保护的共享字段确实有效,因为您的所有代码都在单个线程上运行。但是,我建议使用CancellationToken 模式,以便语义更清晰:当AccessTheWebAsync 完成时,您的代码想要取消 UpdateUIAsync。使用这样的既定模式不仅可以阐明意图,还可以使代码更具可重用性。

【讨论】:

    【解决方案2】:

    async-await 报告进度的方式是通过IProgress Interface 及其实现Progress Class

    如果您将 UpdateUIAsync 方法更改为:

    private async Task UpdateUIAsync(IProgress<string> progress, CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            progress.Report(".");
            await Task.Delay(200);
        }
    }
    

    那么你可以这样使用它:

    private async void StartButton_Click(object sender, RoutedEventArgs e)
    {
        this.resultsTextBox.Text = "Working ";
    
        using (var cts = new CancellationTokenSource())
        {
            var task = AccessTheWebAsync();
            await Task.WhenAny(
                task, 
                UpdateUIAsync(
                    new Progress<string>(s => this.resultsTextBox += s),
                    cts.Token));
            cts.Cancel();
            var contentLength = await task;
        }
    
        this.resultsTextBox.Text +=
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
    }
    

    【讨论】:

      猜你喜欢
      • 2012-06-28
      • 1970-01-01
      • 2023-01-27
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-10
      相关资源
      最近更新 更多