2013-07-31 51 views
17

我已经在BackgroundWorker中使用过这种类型的东西,但我想使用.NET 4.5的新异步/等待方法。我可能会吠叫错误的树。请指教。使用WinForms进行异步/等待ProgressBar

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

状态:请参阅下面的代码。我以为自己做得很好,直到我尝试与窗口交互。如果我把事情放在一边(即不要碰!),一切都会“完美地”运行,但是如果我点击任一窗口,程序就会在长时间运行结束后挂起。实际的交互(拖动)被忽略,就像UI线程被阻塞一样。

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

代码(Form1上是一个标准的形式与进度和公共方法,的UpdateProgress中,设定进度的值):

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<bool> 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; 
     } 
    } 
} 
} 

回答

7

@ 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<int>(value => _progressForm.UpdateProgress(value)); 

    _progressForm.Activated += async (sender, args) => 
     { 
      await Go(progress); 
      _progressForm.Close(); 
     }; 

    Application.Run(_progressForm); 
} 
+0

如果Go(progress)抛出一个异常,那么_progressForm.Close()永远不会被调用 - >模式对话框将永远挂起 – Hiep

+0

并且最好将“_progressForm.Activated”更改为“_progressForm.Shown”,因为_progressForm可能被激活多次在他一生中的时间.. - >去(进展)将被称为多次.. – Hiep

+0

https://gist.github.com/duongphuhiep/f83f98593d93045e717f – Hiep

18

asyncawait关键字并不意味着“在后台运行线。”我有一个async/await intro on my blog,它描述了他们做什么的意思。您必须明确地将CPU绑定操作放在后台线程上,例如Task.Run

此外,Task-based Asynchronous Pattern文档描述了代码的常用方法,例如进度报告。

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<int>(value => _progressForm.UpdateProgress(value)); 
    await Go(progress); 

    _progressForm.Hide(); 
    } 

    private Task<bool> Go(IProgress<int> progress) 
    { 
    return Task.Run(() => 
    { 
     var job = new LongJob(); 
     job.Spin(progress); 
     return true; 
    }); 
    } 
} 

class LongJob 
{ 
    public void Spin(IProgress<int> progress) 
    { 
    for (var i = 1; i <= 100; i++) 
    { 
     Thread.Sleep(25); 
     if (progress != null) 
     { 
     progress.Report(i); 
     } 
    } 
    } 
} 

注意,Progress<T>类型正确处理线程编组,因此没有必要范围内Form1.UpdateProgress编组。

+0

您的更改不会产生所需的结果。由于OP在'Console'应用程序中运行,因此不存在'SynchronizationContext'。我认为需要引入'进度'才能正常工作? – YK1

+0

Windows窗体组件在创建时会建立一个'WinFormsSynchronizationContext','Progress '不需要'SynchronizationContext'(尽管它可以更好地工作)。 –

+0

我把一个断点放到传递给'Progress '的委托中 - 它永远不会被触及 - 虽然工作正在旋转,但UI只是挂起。我认为'Application.Run()'需要建立'SynchronizationContext' - '_progressForm.Show'建立一个吗?我不确定。 – YK1

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

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