インターフェースがあります
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.Run
をTaskEx.Run
に置き換えることができます。上記の例では、最初の質問をより明確にするために、意識的に.NET 4.5メソッドを使用しました。
インターフェースから非同期メソッドを実装する必要があり、実装が同期的である場合、Nedのソリューションを使用できます。
public Task<Bar> CreateBarAsync()
{
return Task.FromResult<Bar>(SynchronousBarCreator());
}
このソリューションでは、メソッドは非同期に見えますが、同期しています。
またはあなたが提案した解決策:
Task<Bar> CreateBarAsync()
{
return Task.Run(() => SynchronousBarCreator());
}
このように、メソッドは本当に非同期です。
「タスクを返すインターフェイスメソッドを実装する方法」のすべてのケースに一致する一般的なソリューションはありません。それはコンテキストに依存します:あなたの実装は十分に速いので、別のスレッドでそれを呼び出すのは役に立たないのですか?このメソッドが呼び出されると、このインターフェイスはどのように使用されますか(アプリがフリーズしますか)?別のスレッドで実装を呼び出すこともできますか?
これを試して:
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync()
{
return Task.FromResult<Bar>(SynchronousBarCreator());
}
}
Task.FromResult
提供された値を使用して、指定されたタイプのすでに完了したタスクを作成します。
.NET 4.0を使用している場合は、TaskCompletionSource<T>
:
Task<Bar> CreateBarAsync()
{
var tcs = new TaskCompletionSource<Bar>();
tcs.SetResult(SynchronousBarCreator());
return tcs.Task
}
最終的に、メソッドに非同期のものがない場合は、新しいCreateBar
を作成する同期エンドポイント(Bar
)を公開することを検討する必要があります。そうすれば、驚きはなく、冗長なTask
でラップする必要もありません。
他の回答を補完するために、もう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;
}
}
_