web-dev-qa-db-ja.com

Task <T>を返すインターフェースメソッドを実装する方法

インターフェースがあります

interface IFoo
{
  Task<Bar> CreateBarAsync();
}

Barを作成するには、非同期と同期の2つの方法があります。これら2つのメソッドそれぞれにインターフェース実装を提供したいと思います。

非同期メソッドの場合、実装は次のようになります。

class Foo1 : IFoo
{
  async Task<Bar> CreateBarAsync()
  {
    return await AsynchronousBarCreatorAsync();
  }
}

しかし、どのようにしてBarを作成するためにsynchronousメソッドを使用するクラスFoo2を実装する必要がありますか?

I could同期的に実行するメソッドを実装します。

  async Task<Bar> CreateBarAsync()
  {
    return SynchronousBarCreator();
  }

コンパイラは、メソッドシグネチャでasyncを使用しないよう警告します。

この非同期メソッドには「待機」演算子がなく、同期的に実行されます。 「await」演算子を使用して非ブロッキングAPI呼び出しを待機するか、「await Task.Run(...)」を使用してバックグラウンドスレッドでCPUにバインドされた作業を行うことを検討してください。

または、私はcouldTask<Bar>を明示的に返すメソッドを実装します。私の意見では、コードは読みにくくなります。

  Task<Bar> CreateBarAsync()
  {
    return Task.Run(() => SynchronousBarCreator());
  }

パフォーマンスの観点から、両方のアプローチがほぼ同じオーバーヘッドを持っていると思いますか、それとも?

どのアプローチを選択する必要がありますか。 asyncメソッドを同期的に実装するか、同期メソッドの呼び出しをTask

[〜#〜]編集[〜#〜]

私が取り組んでいるプロジェクトは、実際にはasync/awaitMicrosoft Async NuGetパッケージの拡張機能を備えた.NET 4プロジェクトです。 .NET 4では、Task.RunTaskEx.Runに置き換えることができます。上記の例では、最初の質問をより明確にするために、意識的に.NET 4.5メソッドを使用しました。

40

インターフェースから非同期メソッドを実装する必要があり、実装が同期的である場合、Nedのソリューションを使用できます。

public Task<Bar> CreateBarAsync()
{
    return Task.FromResult<Bar>(SynchronousBarCreator());
}

このソリューションでは、メソッドは非同期に見えますが、同期しています。

またはあなたが提案した解決策:

  Task<Bar> CreateBarAsync()
  {
    return Task.Run(() => SynchronousBarCreator());
  }

このように、メソッドは本当に非同期です。

「タスクを返すインターフェイスメソッドを実装する方法」のすべてのケースに一致する一般的なソリューションはありません。それはコンテキストに依存します:あなたの実装は十分に速いので、別のスレッドでそれを呼び出すのは役に立たないのですか?このメソッドが呼び出されると、このインターフェイスはどのように使用されますか(アプリがフリーズしますか)?別のスレッドで実装を呼び出すこともできますか?

24
Guillaume

これを試して:

class Foo2 : IFoo
{
    public Task<Bar> CreateBarAsync()
    {
        return Task.FromResult<Bar>(SynchronousBarCreator());
    }
}

Task.FromResult 提供された値を使用して、指定されたタイプのすでに完了したタスクを作成します。

23
Ned Stoyanov

.NET 4.0を使用している場合は、TaskCompletionSource<T>

Task<Bar> CreateBarAsync()
{
    var tcs = new TaskCompletionSource<Bar>();
    tcs.SetResult(SynchronousBarCreator());
    return tcs.Task
}

最終的に、メソッドに非同期のものがない場合は、新しいCreateBarを作成する同期エンドポイント(Bar)を公開することを検討する必要があります。そうすれば、驚きはなく、冗長なTaskでラップする必要もありません。

8
Yuval Itzchakov

他の回答を補完するために、もう1つのオプションがあります。これは.NET 4.0でも機能すると思います。

_class Foo2 : IFoo
{
    public Task<Bar> CreateBarAsync()
    {
        var task = new Task<Bar>(() => SynchronousBarCreator());
        task.RunSynchronously();
        return task;
    }
}
_

task.RunSynchronously()に注意してください。 _Task<>.FromResult_と_TaskCompletionSource<>.SetResult_と比較すると、mightが最も遅いオプションですが、微妙でありながら重要な違いがあります:エラー伝搬動作

上記のアプローチはasyncメソッドの動作を模倣します。例外は同じスタックフレームでスローされることはなく(巻き戻し)、Taskオブジェクト内に休止状態で格納されます。呼び出し元は実際に_await task_または_task.Result_を介してそれを監視する必要があり、その時点で再スローされます。

これは、_Task<>.FromResult_および_TaskCompletionSource<>.SetResult_の場合とは異なります。この場合、SynchronousBarCreatorによってスローされた例外は、呼び出し元に直接伝達され、呼び出しスタックが巻き戻されます。

これについてもう少し詳しく説明します。

"await Task.Run(); return;"と "return Task.Run()"の違いはありますか?

余談ですが、インターフェイスを設計するときはcancellationの規定を追加することをお勧めします(キャンセルが現在使用/実装されていない場合でも)。

_interface IFoo
{
    Task<Bar> CreateBarAsync(CancellationToken token);
}

class Foo2 : IFoo
{
    public Task<Bar> CreateBarAsync(CancellationToken token)
    {
        var task = new Task<Bar>(() => SynchronousBarCreator(), token);
        task.RunSynchronously();
        return task;
    }
}
_
6
noseratio