Async / Await与WinForms ProgressBar

我已经使用BackgroundWorker在过去使用过这种类型的东西,但我想使用.NET 4.5的新异步/等待方法。 我可能正在咆哮错误的树。 请指教。

目标 :创建一个组件,该组件将执行一些长时间运行的工作,并在执行工作时显示带有进度条的模态表单。 该组件将获取窗口的句柄,以便在执行长时间运行的工作时阻止交互。

状态 :请参阅下面的代码。 在我尝试与windows进行交互之前,我认为自己做得很好。 如果我把事情单独处理(即不要触摸!),一切都“完美”运行,但是如果我点击任一窗口,那么程序会在长时间运行结束后挂起。 忽略实际交互(拖动),就像UI线程被阻止一样。

问题 :我的代码可以很容易修复吗? 如果是这样,怎么样? 或者,我应该使用不同的方法(例如BackgroundWorker)吗?

代码 (Form1是带有ProgressBar和公共方法的标准表单,UpdateProgress,用于设置ProgressBar的值):

using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Console.WriteLine("Starting.."); var mgr = new Manager(); mgr.GoAsync(); Console.WriteLine("..Ended"); Console.ReadKey(); } } class Manager { private static Form1 _progressForm; public async void GoAsync() { var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); _progressForm = new Form1(); _progressForm.Show(owner); await Go(); _progressForm.Hide(); } private async Task Go() { var job = new LongJob(); job.OnProgress += job_OnProgress; job.Spin(); return true; } void job_OnProgress(int percent) { _progressForm.UpdateProgress(percent); } } class LongJob { public event Progressed OnProgress; public delegate void Progressed(int percent); public void Spin() { for (var i = 1; i <= 100; i++) { Thread.Sleep(25); if (OnProgress != null) { OnProgress(i); } } } } class Win32Window : IWin32Window { private readonly IntPtr _hwnd; public Win32Window(IntPtr handle) { _hwnd = handle; } public IntPtr Handle { get { return _hwnd; } } } } 

@ StephenCleary的回答是正确的。 虽然,我不得不对他的答案做一点修改,以获得我认为OP想要的行为。

 public void GoAsync() //no longer async as it blocks on Appication.Run { var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); _progressForm = new Form1(); var progress = new Progress(value => _progressForm.UpdateProgress(value)); _progressForm.Activated += async (sender, args) => { await Go(progress); _progressForm.Close(); }; Application.Run(_progressForm); } 

asyncawait关键字并不意味着“在后台线程上运行”。 我在我的博客上有一个async / await介绍 ,描述了他们的意思。 您必须在后台线程上显式放置CPU绑定操作,例如Task.Run

此外, 基于任务的异步模式文档描述了使用async代码的常见方法,例如,进度报告。

 class Manager { private static Form1 _progressForm; public async Task GoAsync() { var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle); _progressForm = new Form1(); _progressForm.Show(owner); var progress = new Progress(value => _progressForm.UpdateProgress(value)); await Go(progress); _progressForm.Hide(); } private Task Go(IProgress progress) { return Task.Run(() => { var job = new LongJob(); job.Spin(progress); return true; }); } } class LongJob { public void Spin(IProgress progress) { for (var i = 1; i <= 100; i++) { Thread.Sleep(25); if (progress != null) { progress.Report(i); } } } } 

请注意, Progress类型正确处理线程编组,因此不需要在Form1.UpdateProgress进行封送处理。

 private async void button1_Click(object sender, EventArgs e) { IProgress progress = new Progress(value => { progressBar1.Value = value; }); await Task.Run(() => { for (int i = 0; i <= 100; i++) progress.Report(i); }); } 

如果我错了,请纠正我,但这似乎是更新进度条的最简单方法。