[解決済み] WPF GUIから非同期タスクを実行し、対話する方法
質問
WPF GUI で、長いタスクを開始するためにボタンを押すと、そのタスクの間ウィンドウがフリーズしてしまうことがあります。タスクが実行されている間、進捗状況のレポートを取得したいのですが、私が選んだいつでもタスクを停止する別のボタンを組み込みたいと思います。
async/await/taskを使用する正しい方法を把握することができません。私が試したすべてを含めることはできませんが、これは私が現在持っているものです。
WPFのウィンドウクラスです。
public partial class MainWindow : Window
{
readonly otherClass _burnBabyBurn = new OtherClass();
internal bool StopWorking = false;
//A button method to start the long running method
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
Task slowBurn = _burnBabyBurn.ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3);
await slowBurn;
}
//A button Method to interrupt and stop the long running method
private void StopButton_Click(object sender, RoutedEventArgs e)
{
StopWorking = true;
}
//A method to allow the worker method to call back and update the gui
internal void UpdateWindow(string message)
{
TextBox1.Text = message;
}
}
そして、ワーカーメソッド用のクラスです。
class OtherClass
{
internal Task ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
var tcs = new TaskCompletionSource<int>();
//Start doing work
gui.UpdateWindow("Work Started");
While(stillWorking)
{
//Mid procedure progress report
gui.UpdateWindow("Bath water n% thrown out");
if (gui.StopTraining) return tcs.Task;
}
//Exit message
gui.UpdateWindow("Done and Done");
return tcs.Task;
}
}
これは実行されますが、Workerメソッドが開始されると、WPF機能ウィンドウはまだブロックされたままです。
async/await/taskの宣言をどのようにアレンジすれば
A) ワーカーメソッドがGUIウィンドウをブロックしないようにする。
B) ワーカーメソッドがguiウィンドウを更新するようにする。
C) guiウィンドウが割り込みを停止し、ワーカーメソッドを停止するようにする。
どんなヘルプやポインタでも大いに結構です。
どのように解決するのですか?
長い話です。
private async void ButtonClickAsync(object sender, RoutedEventArgs e)
{
// modify UI object in UI thread
txt.Text = "started";
// run a method in another thread
await HeavyMethodAsync(txt);
// <<method execution is finished here>>
// modify UI object in UI thread
txt.Text = "done";
}
// This is a thread-safe method. You can run it in any thread
internal async Task HeavyMethodAsync(TextBox textBox)
{
while (stillWorking)
{
textBox.Dispatcher.Invoke(() =>
{
// UI operation goes inside of Invoke
textBox.Text += ".";
// Note that:
// Dispatcher.Invoke() blocks the UI thread anyway
// but without it you can't modify UI objects from another thread
});
// CPU-bound or I/O-bound operation goes outside of Invoke
// await won't block UI thread, unless it's run in a synchronous context
await Task.Delay(51);
}
}
Result:
started....................done
(1)の書き方について知っておく必要があります。
async
のコードの書き方 (2) 別のスレッドでUI操作を実行する方法 (3) タスクをキャンセルする方法について知っておく必要があります。
この記事では(3)のキャンセルの仕組みについては触れません。ただ、この記事では
CancellationTokenSource
を生成し、それによって
CancellationToken
を与え、それを任意のメソッドに渡すことができます。ソースをキャンセルすると、すべてのトークンが知ることができます。
async
そして
await
:
の基本
async
と
await
-
以下の場合のみ可能です。
await
の中にasync
メソッドに追加します。 -
のみが可能です。
await
は、待ち受け可能なオブジェクト(つまりTask
,ValueTask
,Task<T>
,IAsyncEnumerable<T>
など) これらのオブジェクトはasync
メソッドとawait
キーワードはそれらをアンラップします。(ラッピングとアンラッピングのセクションを参照) -
非同期メソッド名は 常に で終わらせる。
Async
で終わるようにすると、読みやすく、間違えにくくなります。// Synchronous method: TResult MethodName(params) { } // Asynchronous method: async Task<TResult> MethodNameAsync(params) { }
の魔法
async
と
await
-
は
async-await
構文機能は、ステートマシンを使って、コンパイラに をあきらめる と 引き取る の制御はawaited Task
でasync
メソッドに追加します。 -
で実行を待ちます。
await
で待ち、その結果を返します。 ブロックすることなく をブロックすることなく、結果を返します。 -
Task.Run
をキューに入れます。Task
の中に スレッドプール . (ただし、それが 純粋な の操作でなければ)。 すなわちasync
メソッド は は別のスレッドで実行されます。async
とawait
はそれ自体ではスレッド生成とは関係ありません。
だから
あなたが
を実行すると
Task
(例
Task.Run(action)
のように)その動作のためにスレッドを(再)使用します。そして、そのタスクを
async
メソッドに入れることで、その流れを制御することができます。を置くことで
async
をつけると、コンパイラにステートマシンを使ってそのメソッドのフローを制御するように指示します(これはスレッド化を意味するものではありません)。また
await
を使うことで、そのメソッド内の実行フローが
await
ステートメント
UIスレッドをブロックすることなく
. もし、フローを呼び出し側に渡したい場合は
async
メソッド自体が
Task
になるので、同じパターンを呼び出し元などにカスケードすることができるようになります。
async Task Caller() { await Method(); }
async Task Method() { await Inner(); }
async Task Inner() { await Task.Run(action); }
イベントハンドラは以下のようなコードになります。
のシグネチャにasyncが存在する場合、2つのケースが考えられます。
ExecuteLongProcedure
(ケース 1 と 2) と
MyButton_ClickAsync
(ケースA,B)について説明します。
private async void MyButton_ClickAsync(object sender, RoutedEventArgs e)
{
//queue a task to run on threadpool
// 1. if ExecuteLongProcedure is a normal method and returns void
Task task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3)
);
// or
// 2. if ExecuteLongProcedure is an async method and returns Task
Task task = ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3);
// either way ExecuteLongProcedure is running asynchronously here
// the method will exit if you don't wait for the Task to finish
// A. wait without blocking the main thread
// -> requires MyButton_ClickAsync to be async
await task;
// or
// B. wait and block the thread (NOT RECOMMENDED AT ALL)
// -> does not require MyButton_ClickAsync to be async
task.Wait();
}
非同期メソッドの戻り値の型。
次のような宣言があったとします。
private async ReturnType MethodAsync() { ... }
-
もし
ReturnType
はTask
ではawait MethodAsync();
が返ってくるvoid
-
もし
ReturnType
はTask<T>
ではawait MethodAsync();
は型の値を返します。T
これは アンラッピング と呼ばれ、次のセクション(Wrapping and Unrwapping)を参照してください。
-
もし
ReturnType
はvoid
できないawait
それ-
もし、あなたが
await MethodAsync();
と書くと、コンパイルエラーが発生します。
cannot await void
-
以下のことが可能です。
だけ
燃やして忘れる
すなわち、普通にメソッドを呼び出すだけです。
MethodAsync();
を呼び出すだけで、あとは自分の人生を歩むだけです。 -
は
MethodAsync
の実行は同期的に行われますが、これはasync
を持つので、そのマジックを利用することができます。await task
をメソッド内に書くことで、実行の流れを制御することができます。 -
これは、WPFがボタンのクリックイベントハンドラを処理する方法です。
明らかに、イベントハンドラが
void
.
-
もし、あなたが
非同期メソッドの戻り値の型は必ず
void
,Task
,Task<T>
のように、タスクのようなタイプです。IAsyncEnumerable<T>
またはIAsyncEnumerator<T>
ラッピングとアンラッピング。
ラッピング。
async
メソッドはその戻り値を
Task
.
例えば、このメソッドは
Task
の周りに
int
で囲み、それを返す。
// async Task<int>
private async Task<int> GetOneAsync()
{
int val = await CalculateStuffAsync();
return val;
// returns an integer
}
ラッピングを解除します。
取得する場合や
アンラップ
である値を
ラップ
の中にある
Task<>
:
-
非同期オプション。
await
-
synchronousオプション。
task.Result
またはtask.GetAwaiter().GetResult()
またはtask.WaitAndUnwrapException()
または読み C#で同期メソッドから非同期メソッドを呼び出すには?
例
await
はラップを解き
int
から
Task
:
Task<int> task = GetOneAsync();
int number = await task;
//int <- Task<int>
ラップとアンラップの異なる方法。
private Task<int> GetNumber()
{
Task<int> task;
task = Task.FromResult(1); // the correct way to wrap a quasi-atomic operation, the method GetNumber is not async
task = Task.Run(() => 1); // not the best way to wrap a number
return task;
}
private async Task<int> GetNumberAsync()
{
int number = await Task.Run(GetNumber); // unwrap int from Task<int>
// bad practices:
// int number = Task.Run(GetNumber).GetAwaiter().GetResult(); // sync over async
// int number = Task.Run(GetNumber).Result; // sync over async
// int number = Task.Run(GetNumber).Wait(); // sync over async
return number; // wrap int in Task<int>
}
まだ迷っていますか?非同期の戻り値の型は MSDN .
タスクの結果をアンラップするには 常に を使うようにしてください。
await
の代わりに.Result
に変更しないと、非同期の利点はなく、非同期の欠点だけが残ることになります。後者は「sync over async"」と呼ばれます。
注意してください。
await
は非同期で
task.Wait()
とは異なります。しかし、どちらもタスクが終了するのを待つという点では同じです。
await
は非同期で
task.Result
とは異なります。しかし、どちらもタスクの終了を待ち、ラップを解除して結果を返すという点では同じです。
ラップされた値を持つには、常に
Task.FromResult(1)
を使って新しいスレッドを作成するのではなく
Task.Run(() => 1)
.
Task.Run
は、より新しく(.NetFX4.5)、よりシンプルなバージョンです。
Task.Factory.StartNew
WPFのGUIです。
ここで説明するのは UI操作を別のスレッドで実行する方法について説明します。
ブロック化する。
まず最初に知っておくべきことは
WPF非同期イベントハンドラ
は
Dispatcher
を提供することになります。
同期コンテキスト
.
ここで説明されている
CPUバウンドまたはIOバウンドの操作、例えば
Sleep
と
task.Wait()
は
ブロックし、消費する
を持つメソッドで呼び出されたとしても、スレッドをブロックし、消費します。
async
キーワードを持つメソッドで呼び出されたとしてもです。
await Task.Delay()
はステートマシンに
停止
を停止させ、スレッドのリソースを消費させないようにします。
private async void Button_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(1000);//stops, blocks and consumes threadpool resources
await Task.Delay(1000);//stops without consuming threadpool resources
Task.Run(() => Thread.Sleep(1000));//does not stop but consumes threadpool resources
await Task.Run(() => Thread.Sleep(1000));//literally the WORST thing to do
}
スレッドの安全性
GUIに非同期でアクセスする必要がある場合(
ExecuteLongProcedure
メソッド内)。
を呼び出します。
スレッドセーフでないオブジェクトへの変更を伴う操作。例えば、WPFのGUIオブジェクトはすべて
Dispatcher
オブジェクトはGUIスレッドに関連付けられます。
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
ただし、タスクの起動が
プロパティ変更コールバック
の結果としてタスクが開始されるのであれば、ViewModel からは
Dispatcher.Invoke
を使用する必要はありません。なぜなら、コールバックは実際には UI スレッドから実行されるからです。
<ブロッククオート非UIスレッドでコレクションにアクセスする
WPF では、コレクションを作成したスレッド以外のスレッドでデータ コレクションにアクセスし、変更することができます。これにより、バックグラウンド スレッドを使用して、データベースなどの外部ソースからデータを受け取り、UI スレッドにデータを表示することができます。別のスレッドを使用してコレクションを変更することで、ユーザー インターフェイスはユーザーの操作に反応したままになります。
INotifyPropertyChanged によって発生した値の変更は、自動的にディスパッチャにマーシャリングされて戻されます。
覚えておいてください。
async
メソッド自体はメインスレッドで実行されます。ですから、これは有効です。
private async void MyButton_ClickAsync(object sender, RoutedEventArgs e)
{
txt.Text = "starting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure1());
txt.Text = "waiting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure2());
txt.Text = "finished"; // UI Thread
}
UI スレッドから UI 操作を呼び出すもう一つの方法として
SynchronizationContext
のように
ここで
.
SynchronizationContext
よりも強い抽象化です。
Dispatcher
よりも強力な抽象化であり、クロスプラットフォームである。
var uiContext = SynchronizationContext.Current;
while (stillWorking)
{
uiContext.Post(o =>
{
textBox.Text += ".";
}, null);
await Task.Delay(51);
}
パターン
ファイア・アンド・フォーゲット・パターン
明らかな理由により、これは、WPFのGUIイベントハンドラである
Button_ClickAsync
のような WPF GUI イベントハンドラが呼び出されます。
void Do()
{
// CPU-Bound or IO-Bound operations
}
async void DoAsync() // returns void
{
await Task.Run(Do);
}
void FireAndForget() // not blocks, not waits
{
DoAsync();
}
火をつけて観察する
処理されない例外が発生した場合、その例外を処理するために
TaskScheduler.UnobservedTaskException
.
void Do()
{
// CPU-Bound or IO-Bound operations
}
async Task DoAsync() // returns Task
{
await Task.Run(Do);
}
void FireAndWait() // not blocks, not waits
{
Task.Run(DoAsync);
}
スレッドリソースを浪費しながら同期的に発火・待機する。
これは、以下のように知られています。
非同期より同期
これは同期処理ですが、複数のスレッドを使用するため、飢餓状態に陥る可能性があります。この現象は
Wait()
から直接結果を読み込もうとしたり
task.Result
から直接結果を読み取ろうとします。
( このパターンを避ける )
void Do()
{
// CPU-Bound or IO-Bound operations
}
async Task DoAsync() // returns Task
{
await Task.Run(Do);
}
void FireAndWait() // blocks, waits and uses 2 more threads. Yikes!
{
var task = Task.Run(DoAsync);
task.Wait();
}
これで終わりですか?
いいえ、まだまだ学ぶべきことはたくさんあります。
async
は、その
コンテキスト
とその
継続
. これは
ブログ記事
は特におすすめです。
TaskがThreadを使う?確かですか?
そうとは限りません。読む
この回答
の素顔をもっと知るために
async
.
スティーブン・クリアリー
は、次のように説明しています。
async-await
を完璧に説明しています。彼はまた、自分の
他のブログ記事
で説明しています。
もっと読む
非同期、並列、並行の違いを確認してください。
また、以下の記事もご覧ください。 シンプルな非同期ファイルライター を読んで、どこで同時進行すべきかを知ってください。
調査する コンカレント名前空間
最終的には、この電子書籍を読んでください。 並列プログラミングのパターン_CSharp
関連
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み】取り消せないメンバはメソッドのように使えない?
-
[解決済み】EF 5 Enable-Migrations : アセンブリにコンテキストタイプが見つかりませんでした
-
[解決済み】Swashbuckle/Swagger + ASP.Net Core: "Failed to load API definition" (API定義の読み込みに失敗しました
-
[解決済み】HRESULTからの例外:0x800A03ECエラー
-
[解決済み] 他のスレッドからGUIを更新するにはどうすればよいですか?
-
[解決済み] エラー:エンティティタイプは主キーを必要とします。
-
[解決済み] async」と「await」の使い方とタイミング
-
[解決済み] 非同期Task<T>メソッドを同期的に実行するにはどうしたらいいですか?
-
[解決済み] RelativeSourceでWPFバインディングを使用するにはどうしたらいいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】プログラム実行中に1秒待つ
-
[解決済み] DBNullから他の型にオブジェクトをキャストすることができない
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】ORA-01008: すべての変数がバインドされていません。これらはバインドされています。
-
[解決済み】取り消せないメンバはメソッドのように使えない?
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】IntPtrとは一体何なのか?
-
[解決済み] C#で同期メソッドから非同期メソッドを呼び出すには?
-
[解決済み] Async/awaitとBackgroundWorkerの比較
-
[解決済み] 非同期メソッドの完了を待つには?