我有一个创建3个任务的场景:
List<Task> tasks = new List<Task>();
tasks.Add(Task.Run(() => DoSomething("fu"));
tasks.Add(Task.Run(() => DoSomething("man"));
tasks.Add(Task.Run(() => DoSomething("chu"));
Task.WaitAll(tasks.ToArray());
每个任务只是将其值传递给另一个名为Report()的方法,该方法尝试将文本附加到单个文本框中。鉴于此处的异步方案,我检查InvokeRequired并使用syncroot对象。
private object syncRoot = new object();
public void DoSomething (string x)
{
Report(x);
}
public void Report(string Message)
{
if (txtLog != null)
{
if (txtLog.InvokeRequired)
{
txtLog.Invoke(new MethodInvoker(() => Report(Message)));
}
else
{
lock (syncRoot)
{
txtLog.AppendText(DateTime.Now.ToString("o") + " :: " + System.Threading.Thread.CurrentThread.ManagedThreadId + " :: " + Message.TrimEnd() + "\r\n");
}
}
}
}
现在,这个Report()代码似乎在其他场景(其中有1个异步任务)中运行良好,但在这种情况下,3个任务中的每个都进入txtLog.Invoke(...)然后挂起。如果我通过调试器暂停执行,我可以看到所有3个只是坐在该行并被标记为已阻止。
但是,我不确定为什么该特定行会被阻止。有什么想法吗?
答案 0 :(得分:2)
可能实际上并不与concreate问题相关,但是您可以从另一个线程返回所需的结果并在UI线程上正确更新文本框,而不是创建从另一个线程访问UI控件的变通方法。
public string Report(string message)
{
// some long calculation which should be executed on other thread
return $"{DateTime.Now:o} :: {CurrentThread.ManagedThreadId} :: {message.TrimEnd()};
}
private async void ButtonClick(object sender, EventArgs e)
{
var tasks = new[]
{
Task.Run(() => Report("fu")),
Task.Run(() => Report("man")),
Task.Run(() => Report("chu")),
};
await Task.WhenAll(tasks);
textLog.Text = tasks.Select(task => task.Result)
.Aggregate(new StringBuilder(),
(builder, result) => builder.AppendLine(result),
builder => builder.ToString());
}
答案 1 :(得分:1)
问题在于Invoke
和WaitAll
的组合。
Waitall
阻止UI线程,直到所有任务完成。
但是您的任务只能在Invoke
(也是一个阻塞操作)完成在UI线程上执行委托时完成。这将永远不会发生,因为UI线程已被阻止,等待任务完成。因此陷入僵局。
解决方案1:
请改用BeginInvoke
。这会异步运行委托,因此在安排委托后立即退出任务,从而释放UI线程来执行这些委托:
public void Report(string Message)
{
if (txtLog != null)
{
if (txtLog.InvokeRequired)
{
txtLog.BeginInvoke(new MethodInvoker(() => Report(Message)));
}
else
{
lock (syncRoot)
{
txtLog.AppendText(DateTime.Now.ToString("o") + " :: " + System.Threading.Thread.CurrentThread.ManagedThreadId + " :: " + Message.TrimEnd() + "\r\n");
}
}
}
}
解决方案2:
另一种解决方案(基于Fabio的建议)将使事件处理程序异步并使用await Task.WhenAll(tasks)
。这通过在等待任务完成时不阻塞UI线程来解决问题。在这种情况下,您可以毫无问题地使用Invoke
:
private async void startButton_Click(object sender, EventArgs e)
{
List<Task> tasks = new List<Task>();
tasks.Add(Task.Run(() => DoSomething("fu")));
tasks.Add(Task.Run(() => DoSomething("man")));
tasks.Add(Task.Run(() => DoSomething("chu")));
await Task.WhenAll(tasks.ToArray());
}
注意:通常使用void
作为异步方法的返回类型是不好的做法。 Winforms中UI事件的异步处理程序需要void
返回类型,因此这里没有选择。