C# 在 WinForms 上使用 async/await 访问 Task.Run 中的 UI 控件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19028374/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Accessing UI controls in Task.Run with async/await on WinForms
提问by Wouter de Kort
I have the following code in a WinForms application with one button and one label:
我在一个带有一个按钮和一个标签的 WinForms 应用程序中有以下代码:
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
await Run();
}
private async Task Run()
{
await Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
});
}
}
}
This is a simplified version of the real application I'm working on. I was under the impression that by using async/await in my Task.Run
I could set the label1.Text
property. However, when running this code I get the error that I'm not on the UI thread and I can't access the control.
这是我正在处理的真实应用程序的简化版本。我的印象是通过在我的 async/await 中使用Task.Run
我可以设置label1.Text
属性。但是,在运行此代码时,我收到错误消息,即我不在 UI 线程上并且无法访问控件。
Why can't I access the label control?
为什么我无法访问标签控件?
回答by Gerrie Schenck
Because it's on a different thread and cross-thread calls aren't allowed.
因为它在不同的线程上并且不允许跨线程调用。
You will need to pass on the "context" to the thread you are starting. See an example here: http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/
您需要将“上下文”传递给您正在启动的线程。在此处查看示例:http: //reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/
回答by Persi
Why do you use Task.Run? that start a new worker thread (cpu bound), and it causes your problem.
为什么要使用 Task.Run?启动一个新的工作线程(cpu 绑定),它会导致您的问题。
you should probably just do that:
你可能应该这样做:
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
await ensure you will continue on the same context except if you use .ConfigureAwait(false);
await 确保您将继续在相同的上下文中,除非您使用 .ConfigureAwait(false);
回答by Sriram Sakthivel
Try this
尝试这个
private async Task Run()
{
await Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
});
label1.Text = "test";
}
Or
或者
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
Or
或者
private async Task Run()
{
var task = Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
});
var continuation = task.ContinueWith(antecedent=> label1.Text = "test",TaskScheduler.FromCurrentSynchronizationContext());
await task;//I think await here is redundant
}
async/await doesn't guarantee that it will run in UI thread. await
will capture the current SynchronizationContext
and continues execution with the captured context once the task completed.
async/await 不保证它会在 UI 线程中运行。await
将捕获当前SynchronizationContext
并在任务完成后使用捕获的上下文继续执行。
So in your case you have a nested await
which is inside Task.Run
hence second await
will capture the context which is not going to be UiSynchronizationContext
because it is being executed by WorkerThread
from ThreadPool
.
因此,在您的情况下,您有一个嵌套的嵌套await
,Task.Run
因此第二个await
将捕获不会出现的上下文,UiSynchronizationContext
因为它是由WorkerThread
from执行的ThreadPool
。
Does this answers your question?
这是否回答了您的问题?
回答by svick
When you use Task.Run()
, you're saing that you don'twant the code to run on the current context, so that's exactly what happens.
当您使用 时Task.Run()
,您是说您不希望代码在当前上下文中运行,所以这正是发生的情况。
But there is no need to use Task.Run()
in your code. Correctly written async
methods won't block the current thread, so you can use them from the UI thread directly. If you do that, await
will make sure the method resumes back on the UI thread.
但是没有必要Task.Run()
在你的代码中使用。正确编写的async
方法不会阻塞当前线程,因此您可以直接从 UI 线程使用它们。如果这样做,await
将确保该方法在 UI 线程上恢复。
This means that if you write your code like this, it will work:
这意味着,如果您像这样编写代码,它将起作用:
private async void button1_Click(object sender, EventArgs e)
{
await Run();
}
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
回答by Metro
Try this:
尝试这个:
replace
代替
label1.Text = "test";
with
和
SetLabel1Text("test");
and add the following to your class:
并将以下内容添加到您的课程中:
private void SetLabel1Text(string text)
{
if (InvokeRequired)
{
Invoke((Action<string>)SetLabel1Text, text);
return;
}
label1.Text = text;
}
The InvokeRequired returns true if you are NOT on the UI thread. The Invoke() method takes the delegate and parameters, switches to the UI thread and then calls the method recursively. You return after the Invoke() call because the method has already been called recursively prior to the Invoke() returning. If you happen to be on the UI thread when the method is called, the InvokeRequired is false and the assignment is performed directly.
如果您不在 UI 线程上,则 InvokeRequired 返回 true。Invoke() 方法接受委托和参数,切换到 UI 线程,然后递归调用该方法。您在 Invoke() 调用之后返回,因为在 Invoke() 返回之前该方法已经被递归调用。如果调用方法时恰好在UI线程上,则InvokeRequired为false,直接执行赋值。
回答by codebased
I am going to give you my latest answer that I have given for async understanding.
我将给你我为异步理解而给出的最新答案。
The solution is as you know that when you are calling async method you need to run as a task.
解决方案正如您所知,当您调用异步方法时,您需要作为任务运行。
Here is a quick console app code that you can use for your reference, it will make it easy for you to understand the concept.
这是一个快速的控制台应用程序代码,您可以使用它作为参考,它将使您更容易理解这个概念。
using System;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
Console.WriteLine("Starting Send Mail Async Task");
Task task = new Task(SendMessage);
task.Start();
Console.WriteLine("Update Database");
UpdateDatabase();
while (true)
{
// dummy wait for background send mail.
if (task.Status == TaskStatus.RanToCompletion)
{
break;
}
}
}
public static async void SendMessage()
{
// Calls to TaskOfTResult_MethodAsync
Task<bool> returnedTaskTResult = MailSenderAsync();
bool result = await returnedTaskTResult;
if (result)
{
UpdateDatabase();
}
Console.WriteLine("Mail Sent!");
}
private static void UpdateDatabase()
{
for (var i = 1; i < 1000; i++) ;
Console.WriteLine("Database Updated!");
}
private static async Task<bool> MailSenderAsync()
{
Console.WriteLine("Send Mail Start.");
for (var i = 1; i < 1000000000; i++) ;
return true;
}
}
Here I am trying to initiate task called send mail. Interim I want to update database, while the background is performing send mail task.
在这里,我试图启动名为发送邮件的任务。临时我想更新数据库,而后台正在执行发送邮件任务。
Once the database update has happened, it is waiting for the send mail task to be completed. However, with this approach it is quite clear that I can run task at the background and still proceed with original (main) thread.
一旦数据库更新发生,它就会等待发送邮件任务完成。但是,通过这种方法,很明显我可以在后台运行任务并仍然继续使用原始(主)线程。