18

我过去曾使用 BackgroundWorker 进行过此类工作,但我想使用 .NET 4.5 的新异步/等待方法。我可能会叫错树。请指教。

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

状态:见下面的代码。我以为我做得很好,直到我尝试与窗户互动。如果我不理会事情(即不要碰!),一切都会“完美地”运行,但如果我只点击任一窗口,程序就会在长时间运行的工作结束后挂起。实际交互(拖动)被忽略,就像 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<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;
        }
    }
}
}
4

3 回答 3

19

asyncandawait关键字并不意味着“在后台线程上运行” 。我的博客上有一个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<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.

于 2013-07-31T14:33:31.673 回答
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);
}
于 2013-07-31T15:31:20.877 回答
6
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);
    });
}

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

于 2015-10-09T10:36:11.560 回答