2013-07-31 97 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(進行)が例外をスローした場合、_progressForm.Close()は呼び出されません - >モーダルダイアログが永久にハングします – Hiep

+0

そして_progressFormがアクティブになる可能性があるため、 "_progressForm.Activated"を "彼の人生の時間中に複数回。 - >行く(進行)は複数回呼び出されます.. – Hiep

+0

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

18

asyncawaitキーワードがバックグラウンドで実行する」という意味ではありません。糸。"私はasync/await intro on my blogを持っています。彼らは何を説明していますを意味します。バックグラウンドスレッドにCPUバインド操作を明示的に配置する必要があります(例:Task.Run)。

また、Task-based Asynchronous Patternのドキュメントでは、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内マーシャリングのための必要はありませんので注意してください。

+0

あなたの変更は、望ましい結果をもたらしません。 OPは 'Console'アプリケーションで動作しているので、' SynchronizationContext'はありません。 'Progress 'が正しく動作するために導入する必要があると思いますか? – YK1

+0

Windows Formsコンポーネントは、作成時に 'WinFormsSynchronizationContext'を確立し、' Progress 'は' SynchronizationContext'を必要としません(ただし、よりうまく動作します)。 –

+0

私はデリゲートに「進捗」を渡しました。ヒットすることはありません。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); 
    }); 
} 

私が間違っているなら、私を修正しますが、これは、プログレスバーを更新する最も簡単な方法のようです。

関連する問題