web-dev-qa-db-ja.com

バックグラウンドスレッドで「非同期」メソッドを実行する

私は通常のメソッドから「非同期」メソッドを実行しようとしています:

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();
}
23
McGarnagle

具体的に例を修正するには:

_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で作成された継続も、親が完了すると自動的に開始されます。

38
Cory Nelson

FromCurrentSynchronizationContextの使用に関する警告:

わかりました、コーリーは答えを書き換える方法を知っています:)。

したがって、主な原因は実際には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");
    }
}
  1. Form1:ThreadUI! - OK
  2. TestA.Begin:ThreadUI! - OK
  3. TestA.End:ThreadUI! - OK
  4. Form1:ThreadUI! - OK
  5. TestA.StartNew:ThreadPool-OK
  6. TestA.ContinuWith:ThreadUI! - OK
  7. TestB.Begin:ThreadUI! - OK
  8. TestB.End:ThreadUI! - OK
  9. TestB.StartNew-予想されるThreadPool:ThreadUI! -予期せぬことがあります!
  10. TestB.ContinueWith1はThreadPool:ThreadUIである必要があります! -予期せぬことがあります!
  11. TestB.ContinueWith2:ThreadUI! - OK

タスクが返されることに注意してください:

  1. 非同期メソッド、
  2. Task.Fatory.StartNew、
  3. Task.Run、

開始できません!彼らはすでにホットなタスクです...

1
ipavlu