DoLoading
ロード操作を実行するためにコンシューマーがサブスクライブできるイベントを公開するコントロールを作成しようとしています。便宜上、イベント ハンドラーを UI スレッドから呼び出して、コンシューマーが自由に UI を更新できるようにする必要がありますが、async/await を使用して、UI スレッドをブロックすることなく長時間実行されるタスクを実行することもできます。
このために、次のデリゲートを宣言しました。
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
これにより、コンシューマーはイベントをサブスクライブできます。
public event AsyncEventHandler<bool> DoLoading;
アイデアは、消費者がそのようにイベントをサブスクライブするということです (この行は UI スレッドで実行されます):
loader.DoLoading += async (s, e) =>
{
for (var i = 5; i > 0; i--)
{
loader.Text = i.ToString(); // UI update
await Task.Delay(1000); // long-running task doesn't block UI
}
};
適切な時点でTaskScheduler
、UI スレッドの を取得し、 に保存してい_uiScheduler
ます。
イベントは、次の行で適切な場合にトリガーされloader
ます (これはランダムなスレッドで発生します)。
this.PerformLoadingActionAsync().ContinueWith(
_ =>
{
// Other operations that must happen on UI thread
},
_uiScheduler);
この行は UI スレッドから呼び出されるのではなく、読み込みの完了時に UI を更新する必要があることに注意してください。そのためContinueWith
、読み込みタスクの完了時に UI タスク スケジューラでコードを実行するために使用しています。
次の方法のいくつかのバリエーションを試しましたが、どれもうまくいきませんでした。
private async Task<Task> PerformLoadingActionAsync()
{
TaskFactory uiFactory = new TaskFactory(_uiScheduler);
// Trigger event on the UI thread and await its execution
Task evenHandlerTask = await uiFactory.StartNew(async () => await this.OnDoLoading(_mustLoadPreviousRunningState));
// This can be ignored for now as it completes immediately
Task commandTask = Task.Run(() => this.ExecuteCommand());
return Task.WhenAll(evenHandlerTask, commandTask);
}
private async Task OnDoLoading(bool mustLoadPreviousRunningState)
{
var handler = this.DoLoading;
if (handler != null)
{
await handler(this, mustLoadPreviousRunningState);
}
}
ご覧のとおり、私は 2 つのタスクを開始してContinueWith
おり、以前から 1 つのタスクを実行してすべてを完了することを期待しています。
はcommandTask
すぐに完了するので、当面は無視できます。私が見ているように、イベントハンドラーを呼び出すメソッドへのeventHandlerTask
呼び出しを待っていて、イベントハンドラー自体を待っていることを考えると、イベントハンドラーが完了する1つだけを完了する必要があります。
ただし、実際に起こっていることはawait Task.Delay(1000)
、イベント ハンドラーの行が実行されるとすぐにタスクが完了していることです。
これはなぜですか?どうすれば期待どおりの動作を得ることができますか?