web-dev-qa-db-ja.com

独自の非同期メソッドを書く

独自の非同期メソッドを「正しい」方法で記述する方法を知りたいです。

私はこのようなasync/awaitパターンを説明する多くの投稿を見てきました:

http://msdn.Microsoft.com/en-us/library/hh191443.aspx

_// Three things to note in the signature: 
//  - The method has an async modifier.  
//  - The return type is Task or Task<T>. (See "Return Types" section.)
//    Here, it is Task<int> because the return statement returns an integer. 
//  - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{ 
    // You need to add a reference to System.Net.Http to declare client.
    HttpClient client = new HttpClient();

    // GetStringAsync returns a Task<string>. That means that when you await the 
    // task you'll get a string (urlContents).
    Task<string> getStringTask = client.GetStringAsync("http://msdn.Microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoIndependentWork();

    // The await operator suspends AccessTheWebAsync. 
    //  - AccessTheWebAsync can't continue until getStringTask is complete. 
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync. 
    //  - Control resumes here when getStringTask is complete.  
    //  - The await operator then retrieves the string result from getStringTask. 
    string urlContents = await getStringTask;

    // The return statement specifies an integer result. 
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value. 
    return urlContents.Length;
}

private void DoIndependentWork()
{
    resultsTextBox.Text += "Working........\r\n";
}
_

これは、次のような機能を既に実装しているすべての.NETメソッドに最適です。

  • System.IO操作
  • データベース操作
  • ネットワーク関連の操作(ダウンロード、アップロード...)

しかし、使用できるメソッドがなく、上記の例のDoIndependentWorkメソッドに大きな負荷がある場合、完了するのにかなり時間がかかる独自のメソッドを作成したい場合はどうすればよいでしょうか。

この方法では次のことができます:

  • 文字列操作
  • 計算
  • 自分のオブジェクトを処理する
  • 集約、比較、フィルタリング、グループ化、処理
  • リスト操作、追加、削除、対処

繰り返しますが、多くの投稿に出くわしましたが、人々は次のことだけをしています(再び上記の例を取り上げます)。

_async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

    Task<string> getStringTask = client.GetStringAsync("http://msdn.Microsoft.com");

    await DoIndependentWork();

    string urlContents = await getStringTask;

    return urlContents.Length;
}

private Task DoIndependentWork()
{
    return Task.Run(() => {

        //String manipulations
        //Calculations
        //Handling my own objects
        //Aggregating, comparing, filtering, grouping, handling stuff
        //List operations, adding, removing, coping
    });
}
_

DoIndependentWorkがTaskを返し、AccessTheWebAsyncタスクでメソッドがawaitを取得したことが変更点に気付くかもしれません。

重い負荷操作はTask.Run()内にカプセル化されました。これで十分です。それがすべてである場合、ライブラリ内のすべてのメソッドに非同期メソッドを提供するために必要なことは次のとおりです:

_public class FooMagic
{
    public void DoSomeMagic()
    {
        //Do some synchron magic...
    }

    public Task DoSomeMagicAsync()
    {
        //Do some async magic... ?!?
        return Task.Run(() => { DoSomeMagic(); });
    }
}
_

次のような高得票の質問でも私に説明できれば素晴らしいでしょう: 単純な非同期メソッドの書き方? は、既存のメソッドで説明し、このコメントのようなasyn/awaitパターンを使用するだけです上記の質問のポイントにそれをもたらします: 簡単な非同期メソッドを書く方法?

40
Rand Random

実際の回答

TaskCompletionSource を使用してこれを行います。 Promise Task はコードを実行せず、

「タスクのプロデューサー側がデリゲートにバインドされていないことを表し、タスクプロパティを介してコンシューマー側へのアクセスを提供します。」

非同期操作を開始するとそのタスクを呼び出し元に返し、終了すると結果(または例外/キャンセル)を設定します。操作が本当に非同期であることを確認するのはあなた次第です。

Stephen ToubのAsyncManualResetEvent 実装におけるこの種のすべての非同期のルートメソッドの良い例です:

class AsyncManualResetEvent 
{ 
    private volatile TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();

    public Task WaitAsync() { return _tcs.Task; } 
    public void Set() { _tcs.TrySetResult(true); } 
    public void Reset() 
    { 
        while (true) 
        { 
            var tcs = _tcs; 
            if (!tcs.Task.IsCompleted || 
                Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs) 
                return; 
        } 
    } 
}

バックグラウンド

async-awaitを使用する理由は基本的に2つあります。

  1. スケーラビリティの改善I/O集中的な作業(またはその他の本質的に非同期操作)がある場合、非同期で呼び出すことができるため、呼び出しを解放しますスレッドとそれは他の仕事をその間に行うことができます。
  2. オフロード:CPUの集中的な作業がある場合は、非同期で呼び出すことができます。 GUIスレッドに使用されます)。

したがって、ほとんどの.Netフレームワークの非同期呼び出しは、すぐにasyncをサポートし、オフロードにはTask.Runを使用します(例のように)。 asyncを自分で実装する必要がある必要がある唯一のケースは、新しい非同期呼び出し(I/Oまたはasync 同期コンストラクト など)。

これらのケースは非常にまれであるため、ほとんどの場合、次のような答えが見つかります。

「既存のメソッドとasync/awaitパターンを使用してのみ説明します。」


The Nature of TaskCompletionSource でさらに深くすることができます。

24
i3arnon

簡単な非同期メソッドの書き方を説明していただければ嬉しいです。

まず、asyncメソッドの意味を理解する必要があります。 asyncメソッドを使用するエンドユーザーにasyncメソッドを公開すると、「聞いて、このメソッドはpromise近い将来に完成すること」。それがユーザーに保証していることです。

ここで、Taskがこの「約束」を可能にする方法を理解する必要があります。あなたの質問で尋ねたように、なぜ私のメソッド内にTask.Runを追加するだけで、awaitキーワードを使用して待機することが有効になるのですか?

TaskGetAwaiterパターンを実装します。つまり、awaiterというオブジェクトを返します(実際には TaskAwaiter と呼ばれます)。 TaskAwaiterオブジェクトは、 INotifyCompletion または ICriticalNotifyCompletion インターフェイスを実装し、OnCompletedメソッドを公開します。

これらのすべての利点は、awaitキーワードが使用されると、コンパイラによって使用されます。コンパイラーは、設計時に、オブジェクトがGetAwaiterを実装し、それを使用してコードをステートマシンにコンパイルすることを確認します。これにより、プログラムは、待機してから呼び出し元に制御を戻すことができます。その作業が完了したら再開します。

今、従うべきいくつかのガイドラインがあります。真の非同期メソッドは、背後で余分なスレッドを使用してジョブを実行しません(Stephan Clearyが There Is No Thread )でこれを素晴らしく説明しています内部でTask.Runを使用するメソッドを公開することは、APIのコンシューマーに少し誤解を招く可能性があります。これは、タスクに余分なスレッドが関与しないと想定するためです。 APIを同期的に公開し、ユーザーがTask.Runを使用してAPIをオフロードし、実行フローを制御できるようにする必要があります。

asyncメソッドは主にI/O Bound操作に使用されます。これは、これらのメソッドがIO操作が実行されているため、ハードドライブコール、ネットワークコールなどのIO操作を実行するクラスで多く見られます。

Parallel PFXチームの記事を読むことをお勧めします 同期メソッドの非同期ラッパーを公開する必要がありますか? これは、あなたがやろうとしていることとそれが推奨されない理由を正確に説明しています。

24
Yuval Itzchakov

TL; DR:

Task.Run()はあなたが望むものですが、あなたのライブラリでそれを隠すことに注意してください。

私は間違っているかもしれませんが、 CPUバインドコードを非同期で実行する [by Stephen Cleary]に関するガイダンスを探しているかもしれません。私もこれを見つけるのに苦労しました、そしてそれがとても難しい理由は、あなたがライブラリのためにやるべきことではないのだと思います-ちょっと...

論文

リンクされた記事は、APIの一部としてTask.Run()を使用する場合とUIスレッドをブロックしないために使用する場合の方法と理由についてかなり詳細に説明されています。 -そして、人々が非同期に実行したい長期実行プロセスの2つの「タイプ」を区別します。

  • CPUにバインドされたプロセス(クランチ/実際に動作しており、その動作を行うには別のスレッドが必要なもの)
  • 完全に非同期な操作(IOバインドと呼ばれることもあります-アクションの間に待機時間の束であちこちでいくつかのことを行っており、スレッドが何もしていない状態でスレッドを占有しない方が良いでしょう)。

この記事では、さまざまなコンテキストでのAPI関数の使用について説明し、関連するアーキテクチャが同期または非同期メソッドを「優先」するかどうか、および同期および非同期メソッドシグネチャを持つAPIが開発者に「見える」方法について説明します。

回答

最後のセクション「OK、間違った解決策については十分ですか?これを正しい方法でどのように修正しますか?」はあなたが思うと思うことに入りますこれで終わる、尋ねる:

結論:メソッドの実装でTask.Runを使用しないでください。代わりに、Task.Runを使用してメソッドを呼び出します。

基本的に、Task.Run()はスレッドを「ホグ」するため、CPUにバインドされた作業に使用しますが、使用されるwhereになります。多くの作業を必要とすることをしようとしていて、UIスレッドをブロックしたくない場合は、Task.Run()を使用してハードワーク関数を直接実行します(つまり、イベントハンドラーまたはUIベースのコード内):

class MyService
{
  public int CalculateMandelbrot()
  {
    // Tons of work to do in here!
    for (int i = 0; i != 10000000; ++i)
      ;
    return 42;
  }
}

...

private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => myService.CalculateMandelbrot());
}

しかし...do n't基本的にすべての-Async関数は完全に非同期であるため、CPUバウンド関数である場合、-Asyncという接尾辞が付いたAPI関数でTask.Run()を非表示にしますCPUバウンドではありません。

// Warning: bad code!
class MyService
{
  public int CalculateMandelbrot()
  {
    // Tons of work to do in here!
    for (int i = 0; i != 10000000; ++i)
      ;
    return 42;
  }

  public Task<int> CalculateMandelbrotAsync()
  {
    return Task.Run(() => CalculateMandelbrot());
  }
}

言い換えれば、CPUにバインドされた関数-Asyncを呼び出さないでください。ユーザーはそれがIOにバインドされていると想定するため、Task.Run()を使用して非同期で呼び出し、他のユーザーが適切だと感じたときに同じことをできるようにします。または、意味のある別の名前を付けます(BeginAsyncIndependentWork()またはStartIndependentWorkTask())。

13
Code Jockey