在WinForms上使用async / await访问Task.Run中的UI控件
我在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"; }); } } }
这是我正在处理的实际应用程序的简化版本。 我的印象是,通过在Task.Run
使用async / await我可以设置label1.Text
属性。 但是,当运行此代码时,我得到错误,我不在UI线程上,我无法访问该控件。
为什么我无法访问标签控件?
当你使用Task.Run()
,你不希望代码在当前上下文中运行,所以这正是发生的事情。
但是没有必要在代码中使用Task.Run()
。 正确编写的async
方法不会阻塞当前线程,因此您可以直接在UI线程中使用它们。 如果这样做, await
将确保该方法在UI线程上恢复。
这意味着如果您编写这样的代码,它将起作用:
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"; }
试试这个
private async Task Run() { await Task.Run(async () => { await File.AppendText("temp.dat").WriteAsync("a"); }); label1.Text = "test"; }
要么
private async Task Run() { await File.AppendText("temp.dat").WriteAsync("a"); label1.Text = "test"; }
要么
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不保证它将在UI线程中运行。 await
将捕获当前的SynchronizationContext
并在任务完成后继续执行捕获的上下文。
因此,在您的情况下,您有一个嵌套的await
,它位于Task.Run
因此第二个await
将捕获不会是UiSynchronizationContext
的上下文,因为它正在由ThreadPool
的WorkerThread
执行。
这回答了你的问题吗?
试试这个:
更换
label1.Text = "test";
同
SetLabel1Text("test");
并将以下内容添加到您的类:
private void SetLabel1Text(string text) { if (InvokeRequired) { Invoke((Action)SetLabel1Text, text); return; } label1.Text = text; }
如果您不在UI线程上,则InvokeRequired返回true。 Invoke()方法获取委托和参数,切换到UI线程,然后递归调用该方法。 在Invoke()调用之后返回,因为在Invoke()返回之前已经递归调用了该方法。 如果您在调用方法时碰巧在UI线程上,则InvokeRequired为false,并直接执行赋值。
为什么使用Task.Run? 启动一个新的工作线程(cpu绑定),它会导致你的问题。
你可能应该这样做:
private async Task Run() { await File.AppendText("temp.dat").WriteAsync("a"); label1.Text = "test"; }
等待确保您将继续使用相同的上下文,除非您使用.ConfigureAwait(false);
因为它位于不同的线程上,所以不允许跨线程调用。
您需要将“上下文”传递给您正在启动的线程。 请在此处查看示例: http : //reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/
我将给你我最新的答案,我已经给出了异步理解。
解决方法就像您所知,当您调用异步方法时,您需要作为任务运行。
这是一个快速的控制台应用程序代码,您可以使用它作为参考,它将使您易于理解这个概念。
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 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 MailSenderAsync() { Console.WriteLine("Send Mail Start."); for (var i = 1; i < 1000000000; i++) ; return true; } }
在这里,我尝试启动称为发送邮件的任务。 临时我想更新数据库,而后台正在执行发送邮件任务。
数据库更新发生后,它正在等待发送邮件任务完成。 但是,使用这种方法很明显我可以在后台运行任务并继续使用原始(主)线程。
- 使用异步等待实现具有同步和异步API的库以实现相同的function
- 如果引发任何exception,如何在Task.WhenAll上取消并引发exception?
- async-await的延续突发 – 表现不同?
- 如何复制HttpContent async和cancelable?
- async等待执行windows phone 8
- System.Messaging – 为什么MessageQueue不提供异步版本的Send
- 带有C#驱动程序2.0的MongoDB(服务器v 2.6.7):如何从InsertOneAsync获取结果
- .GetAwaiter()和ConfigureAwait()之间的区别
- async和await关键字不会导致创建其他线程吗?