18

過去に BackgroundWorker でこの種の作業を行ったことがありますが、.NET 4.5 の新しい async/await アプローチを使用したいと考えています。私は間違った木を吠えているかもしれません。お知らせ下さい。

目標: 長時間実行される作業を実行し、作業中にプログレス バーを含むモーダル フォームを表示するコンポーネントを作成します。コンポーネントは、ウィンドウへのハンドルを取得して、長時間実行される作業の実行中に対話をブロックします。

ステータス: 以下のコードを参照してください。ウィンドウを操作してみるまでは、うまくやっていると思っていました。放っておくと (つまり、触れないでください!)、すべてが「完全に」実行されますが、いずれかのウィンドウをクリックするだけで、長時間の作業が終了した後にプログラムがハングします。UI スレッドがブロックされているかのように、実際の操作 (ドラッグ) は無視されます。

質問: 私のコードはかなり簡単に修正できますか? もしそうなら、どのように?または、別のアプローチ (BackgroundWorker など) を使用する必要がありますか?

コード(Form1 は、ProgressBar と、ProgressBar の値を設定するパブリック メソッド 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;
        }
    }
}
}
4

3 に答える 3

19

asyncandキーワードは、「バックグラウンド スレッドで実行する」という意味ではawaitありません。私のブログには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 に答える