私は通常のメソッドから「非同期」メソッドを実行しようとしています:
public string Prop
{
get { return _prop; }
set
{
_prop = value;
RaisePropertyChanged();
}
}
private async Task<string> GetSomething()
{
return await new Task<string>( () => {
Thread.Sleep(2000);
return "hello world";
});
}
public void Activate()
{
GetSomething.ContinueWith(task => Prop = task.Result).Start();
// ^ exception here
}
スローされる例外は次のとおりです。
継続タスクでStartを呼び出すことはできません。
とにかく、それはどういう意味ですか?バックグラウンドスレッドで非同期メソッドを実行し、その結果をUIスレッドにディスパッチする方法を教えてください。
編集
試してみましたTask.Wait
、しかし待機は終わらない:
public void Activate()
{
Task.Factory.StartNew<string>( () => {
var task = GetSomething();
task.Wait();
// ^ stuck here
return task.Result;
}).ContinueWith(task => {
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
GetSomething.ContinueWith(task => Prop = task.Result).Start();
}
具体的に例を修正するには:
_public void Activate()
{
Task.Factory.StartNew(() =>
{
//executes in thread pool.
return GetSomething(); // returns a Task.
}) // returns a Task<Task>.
.Unwrap() // "unwraps" the outer task, returning a proxy
// for the inner one returned by GetSomething().
.ContinueWith(task =>
{
// executes in UI thread.
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
_
これは機能しますが、古いものです。
バックグラウンドスレッドで何かを実行してUIスレッドにディスパッチする現代的な方法は、Task.Run()
、async
、およびawait
を使用することです。
_async void Activate()
{
Prop = await Task.Run(() => GetSomething());
}
_
_Task.Run
_は、スレッドプールスレッドで何かを開始します。何かをawait
すると、それを開始した実行コンテキストに自動的に戻ります。この場合、UIスレッド。
通常、Start()
を呼び出す必要はありません。 async
メソッド、_Task.Run
_、_Task.Factory.StartNew
_を優先します。これらはすべて、タスクを自動的に開始します。 await
またはContinueWith
で作成された継続も、親が完了すると自動的に開始されます。
わかりました、コーリーは答えを書き換える方法を知っています:)。
したがって、主な原因は実際にはFromCurrentSynchronizationContextです!この種のスケジューラでStartNewまたはContinueWithを実行すると、UIスレッドで実行されます。考えられるかもしれません:
OK、UIで後続の操作を開始し、いくつかのコントロールを変更して、いくつかの操作を生成しましょう。しかし、現在TaskScheduler.Currentはnullではなく、コントロールにイベントがある場合、ThreadPoolで実行されることを期待してStartNewが生成され、そこからエラーが発生します。 UI apsは通常、複雑で、確実性を維持するのが難しいため、別のStartNew操作を呼び出すものはありません。ここでは簡単な例を示します。
public partial class Form1 : Form
{
public static int Counter;
public static int Cnt => Interlocked.Increment(ref Counter);
private readonly TextBox _txt = new TextBox();
public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}");
public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "ThreadUI!";
//this seems to be so Nice :)
_txt.TextChanged += (sender, args) => { TestB(); };
WriteTrace("Form1"); TestA(); WriteTrace("Form1");
}
private void TestA()
{
WriteTrace("TestA.Begin");
Task.Factory.StartNew(() => WriteTrace("TestA.StartNew"))
.ContinueWith(t =>
{
WriteTrace("TestA.ContinuWith");
_txt.Text = @"TestA has completed!";
}, TaskScheduler.FromCurrentSynchronizationContext());
WriteTrace("TestA.End");
}
private void TestB()
{
WriteTrace("TestB.Begin");
Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith2"));
WriteTrace("TestB.End");
}
}
タスクが返されることに注意してください:
開始できません!彼らはすでにホットなタスクです...