我被迫等待。我希望我的任务能够通过时尚方式报告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}";
}
}
}
答案 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}";
}