待っているときDispatcher.RunAsync
継続は、作業が完了したときではなく、作業がスケジュールされたときに発生します。作業が完了するのをどのように待つことができますか?
編集
私の最初の質問は、時期尚早の継続がAPIの設計によって引き起こされたと想定していたので、ここに本当の質問があります。
待っているときDispatcher.RunAsync
非同期デリゲートを使用し、デリゲートのコード内でawait
を使用すると、作業が完了したときではなく、await
が検出されたときに継続が発生します。作業が完了するのをどのように待つことができますか?
編集2
すでにUIスレッド上にある作業をディスパッチする必要がある理由の1つは、微妙なタイミングとレイアウトの問題を回避するためです。ビジュアルツリー内の要素のサイズと位置の値が流動的であるのは非常に一般的であり、UIのその後の反復のために作業をスケジュールすることが役立つ場合があります。
Microsoft githubリポジトリ で次の提案を見つけました:方法 バックグラウンドスレッドから送信されるUIタスクを待つ 。
CoreDispatcher
のこの拡張メソッドを定義します。
using System;
using System.Threading.Tasks;
using Windows.UI.Core;
public static class DispatcherTaskExtensions
{
public static async Task<T> RunTaskAsync<T>(this CoreDispatcher dispatcher,
Func<Task<T>> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal)
{
var taskCompletionSource = new TaskCompletionSource<T>();
await dispatcher.RunAsync(priority, async () =>
{
try
{
taskCompletionSource.SetResult(await func());
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
});
return await taskCompletionSource.Task;
}
// There is no TaskCompletionSource<void> so we use a bool that we throw away.
public static async Task RunTaskAsync(this CoreDispatcher dispatcher,
Func<Task> func, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) =>
await RunTaskAsync(dispatcher, async () => { await func(); return false; }, priority);
}
これを行ったら、新しいRunTaskAsync
メソッドを使用して、バックグラウンドタスクをUIの動作で待機させるだけです。
これがUIスレッドで実行する必要のあるメソッドであるとしましょう。次のフローに従うのに役立つデバッグステートメントに注意してください。
public static async Task<string> ShowMessageAsync()
{
// Set up a MessageDialog
var popup = new Windows.UI.Popups.MessageDialog("Question", "Please pick a button to continue");
popup.Commands.Add(new Windows.UI.Popups.UICommand("Button 1"));
popup.Commands.Add(new Windows.UI.Popups.UICommand("Button 2"));
popup.CancelCommandIndex = 0;
// About to show the dialog
Debug.WriteLine("Waiting for user choice...");
var command = await popup.ShowAsync();
// Dialog has been dismissed by the user
Debug.WriteLine("User has made a choice. Returning result.");
return command.Label;
}
バックグラウンドスレッドからそれを待つには、次のようにRunTaskAsync
を使用します。
// Background thread calls this method
public async void Object_Callback()
{
Debug.WriteLine("Object_Callback() has been called.");
// Do the UI work, and await for it to complete before continuing execution
var buttonLabel = await Dispatcher.RunTaskAsync(ShowMessageAsync);
Debug.WriteLine($"Object_Callback() is running again. User clicked {buttonLabel}.");
}
出力は次のようになります。
Object_Callback()が呼び出されました。
ユーザーの選択を待っています...
ユーザーが選択しました。結果を返します。
Object_Callback()が再び実行されています。ユーザーがボタン1をクリックしました。
あなたの質問は、あなたが仕事をスケジュールする(そして待つ)ことを想定していますon UIスレッドfromバックグラウンドスレッド。
[〜#〜] ui [〜#〜]が「マスター」である場合、通常、コードははるかにクリーンで理解しやすい(そして間違いなく移植性が高い)ことがわかります。バックグラウンドスレッドは「スレーブ」です。
したがって、バックグラウンドスレッドawait
を使用する代わりに、UIスレッドが実行する操作(厄介で移植性のないDispatcher.RunAsync
)、UIスレッドawait
バックグラウンドスレッドが実行する操作があります(ポータブルで非同期用に作成されたTask.Run
)。
RunAsync
への呼び出しを、待機可能な独自の非同期メソッドでラップし、タスクの完了を制御して、呼び出し元の待機の継続を自分で制御できます。
Async-awaitはTask
タイプを中心としているため、このタイプを使用して作業を調整する必要があります。ただし、通常、Task
はスレッドプールスレッドで実行するようにスケジュールするため、UI作業のスケジュールには使用できません。
ただし、TaskCompletionSource
タイプは、予定外のTask
に対する一種の操り人形師として機能するように考案されました。言い換えると、TaskCompletionSource
は何もするようにスケジュールされていないダミーのTask
を作成できますが、TaskCompletionSource
のメソッドを介して、通常のように実行および完了しているように見える場合がありますジョブ。
この例を参照してください。
public Task PlayDemoAsync()
{
var completionSource = new TaskCompletionSource<bool>();
this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
try
{
foreach (var ppc in this.Plots.Select(p => this.TransformPlot(p, this.RenderSize)))
{
// For each subsequent stroke plot, we need to start a new figure.
//
if (this.Sketch.DrawingPoints.Any())
this.Sketch.StartNewFigure(ppc.First().Position);
foreach (var point in ppc)
{
await Task.Delay(100);
this.Sketch.DrawingPoints.Add(point.Position);
}
}
completionSource.SetResult(true);
}
catch (Exception e)
{
completionSource.SetException(e);
}
});
return (Task)completionSource.Task;
}
注:UIスレッドで行われている主な作業は、100ミリ秒ごとに画面に描画される線だけです。
TaskCompletionSource
がパペットマスターとして作成されます。終わり近くを見ると、呼び出し元に返されるTask
プロパティがあることがわかります。 Task
を返すと、コンパイラのニーズが満たされ、メソッドが待機可能で非同期になります。
ただし、Task
は単なる操り人形であり、UIスレッドで行われている実際の作業のプロキシです。
そのメインUIデリゲートで、TaskCompletionSource.SetResult
メソッドを使用して結果をTask
に強制し(呼び出し元に返されたため)、作業が終了したことを通知する方法を確認してください。
エラーが発生した場合は、SetException
を使用して「別の文字列をプル」し、パペットTask
で例外が発生したように見せます。
Async-awaitサブシステムは違いを認識していないため、期待どおりに機能します。
編集
Svickによって促されたように、メソッドがUIスレッドからのみ呼び出すことができるように設計されている場合は、これで十分です。
/// <summary>
/// Begins a demonstration drawing of the asterism.
/// </summary>
public async Task PlayDemoAsync()
{
if (this.Sketch != null)
{
foreach (var ppc in this.Plots.Select(p => this.TransformPlot(p, this.RenderSize)))
{
// For each subsequent stroke plot, we need to start a new figure.
//
if (this.Sketch.DrawingPoints.Any())
this.Sketch.StartNewFigure(ppc.First().Position);
foreach (var point in ppc)
{
await Task.Delay(100);
this.Sketch.DrawingPoints.Add(point.Position);
}
}
}
}
@StephenClearyが提案するクリーンな方法で作業するための優れた方法は、何らかの理由でワーカースレッドから開始する必要がある場合でも、単純なヘルパーオブジェクトを使用することです。以下のオブジェクトを使用して、次のようなコードを記述できます。
await DispatchToUIThread.Awaiter;
// Now you're running on the UI thread, so this code is safe:
this.textBox.Text = text;
App.OnLaunchedで、オブジェクトを初期化する必要があります。
DispatchToUIThread.Initialize(rootFrame.Dispatcher);
以下のコードの背後にある理論は 何でも待つ; で見つけることができます
public class DispatchToUIThread : INotifyCompletion
{
private readonly CoreDispatcher dispatcher;
public static DispatchToUIThread Awaiter { get; private set; }
private DispatchToUIThread(CoreDispatcher dispatcher)
{
this.dispatcher = dispatcher;
}
[CLSCompliant(false)]
public static void Initialize(CoreDispatcher dispatcher)
{
if (dispatcher == null) throw new ArgumentNullException("dispatcher");
Awaiter = new DispatchToUIThread(dispatcher);
}
public DispatchToUIThread GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return this.dispatcher.HasThreadAccess; }
}
public async void OnCompleted(Action continuation)
{
if (continuation == null) throw new ArgumentNullException("continuation");
await this.dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => continuation());
}
public void GetResult() { }
}