GUI被冻结,直到所有任务完成

时间:2016-02-25 15:53:01

标签: c# async-await

我被迫等待。我希望我的任务能够通过时尚方式报告gui的一些进展 - ContinueWith和FromCurrentSynchronizationContext。

但是GUI被阻止并且不会刷新,直到所有任务都完成。我该如何解决这个问题?

我认为原因是因为任务在同一个池中运行,并且刷新gui任务被添加到队列的末尾。但是,由于缺乏经验,我不知道如何正确地做到这一点

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Linq;
using System.Linq.Expressions;

namespace AsyncCallbackSample
{
    public partial class MainForm : Form
    {
        private readonly Random _random = new Random();

        public MainForm()
        {
            InitializeComponent();
        }

        private async void OnGoButtonClick(object sender, EventArgs e)
        {
            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            await Task.WhenAll(_listBox.Items
                .OfType<string>() 
                .Select(
                    taskArgument => 
                    Task
                        .FromResult(DoLongTermApplication(taskArgument))
                        .ContinueWith(previousTask => _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = previousTask.Result, uiScheduler) // refreshing the gui part while all other staff is in progress.
                        )
                .ToArray());
        }

        private string DoLongTermApplication(string taskInformation)
        {
            Thread.Sleep(1000 + _random.Next(1000));
            return $"Processed {taskInformation}";
        }
    }
}

2 个答案:

答案 0 :(得分:3)

您应遵循以下准则:

  • 永远不要使用ContinueWith。请改用await
  • 请勿使用TaskScheduler,除非您绝对拥有。请改用await
  • 使用Task.Run在线程池线程上运行同步代码。 Task.FromResult不适合此。

结合这些:

private async void OnGoButtonClick(object sender, EventArgs e)
{
  await Task.WhenAll(_listBox.Items.OfType<string>() 
      .Select(taskArgument => ProcessAsync(taskArgument)));
}

private async Task ProcessAsync(string taskArgument)
{
  var result = await Task.Run(() => DoLongTermApplication(taskArgument));
  _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = result;
}

private string DoLongTermApplication(string taskInformation)
{
  Thread.Sleep(1000 + _random.Next(1000));
  return $"Processed {taskInformation}";
}

或者,如果您的DoLongTermApplication可以真正异步(例如,将Thread.Sleep替换为Task.Delay),那么您也不需要Task.Run

private async void OnGoButtonClick(object sender, EventArgs e)
{
  await Task.WhenAll(_listBox.Items.OfType<string>() 
      .Select(taskArgument => ProcessAsync(taskArgument)));
}

private async Task ProcessAsync(string taskArgument)
{
  var result = await DoLongTermApplicationAsync(taskArgument);
  _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = result;
}

private async Task<string> DoLongTermApplicationAsync(string taskInformation)
{
  await Task.Delay(1000 + _random.Next(1000)).ConfigureAwait(false);
  return $"Processed {taskInformation}";
}

由于您是async的新用户,我建议您阅读我的async intro blog post并跟进我的MSDN article on async best practices

答案 1 :(得分:2)

如果要阻止当前(在您的案例中为UI)线程,请使用Thread.Sleep。 请参阅:When to use Task.Delay, when to use Thread.Sleep?

尝试以下几点:

 private async void OnGoButtonClick(object sender, EventArgs e)
 {
            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            await Task.WhenAll(_listBox.Items
                .OfType<string>() 
                .Select(
                    taskArgument => 
                    Task.Run(async () => await DoLongTermApplicationAsync(taskArgument))
                        .ContinueWith(previousTask => _listBox.Items[_listBox.Items.IndexOf(taskArgument)] = previousTask.Result, uiScheduler) // refreshing the gui part while all other staff is in progress.
                        )
                .ToArray());
   }

   private async Task<string> DoLongTermApplicationAsync(string taskInformation)
   {
        await Task.Delay(1000 + _random.Next(1000));
        return $"Processed {taskInformation}";
   }