私は過去にこのタイプのことをBackgroundWorkerで機能させましたが、.NET 4.5の新しい非同期/待機アプローチを使用したいと思います。間違った木を吠えているかもしれません。お知らせ下さい。
目標:長時間実行される作業を行うコンポーネントを作成し、作業を行っているときに進行状況バーでモーダルフォームを表示します。コンポーネントはウィンドウへのハンドルを取得して、長時間実行されている作業の実行中に相互作用をブロックします。
Status:以下のコードを参照してください。窓とのやり取りをするまでは、うまくやっていると思いました。そのままにしておくと(つまり、触らないでください!)、すべてが「完全に」実行されますが、どちらかのウィンドウをクリックするほど、長時間の作業が終了した後にプログラムがハングします。 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;
}
}
}
}
@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);
}
async
およびawait
キーワードは、「バックグラウンドスレッドで実行する」という意味ではありません。私は async
/await
ブログのイントロ を持っていますdo平均。 Task.Run
のように、CPUにバインドされた操作をバックグラウンドスレッドに明示的に配置する必要があります。
また、 タスクベースの非同期パターン のドキュメントでは、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
内でマーシャリングを行う必要はありません。
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);
});
}
私が間違っている場合は修正してください。ただし、これが進行状況バーを更新する最も簡単な方法のようです。