web-dev-qa-db-ja.com

C#で非同期メソッドをどのように作成しますか?

私が読んだすべてのブログ記事では、C#で非同期メソッドを使用する方法について説明していますが、奇妙な理由で、独自の非同期メソッドを作成して使用する方法を説明することは決してありません。だから今私は私の方法を消費するこのコードを持っています:

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

そして私はCountToAsyncであるこのメソッドを書きました:

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

これはTask.Factoryの使用、非同期メソッドを書くための最善の方法、それとも別の方法で書くべきですか?

173

あなたがそのレベルの複雑さを必要としない限り、私はStartNewを推薦しません。

非同期メソッドが他の非同期メソッドに依存している場合、最も簡単な方法はasyncキーワードを使用することです。

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

あなたの非同期メソッドがCPUの仕事をしているなら、あなたはTask.Runを使うべきです:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

私の async/awaitイントロ が役に立つかもしれません。

205
Stephen Cleary

メソッド内でasync/awaitを使用したくないが、外側からawaitキーワードを使用できるようにそれを「装飾」する場合は、 TaskCompletionSource.cs

public static Task<T> RunAsync<T>(Func<T> function)
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>          
    { 
        try 
        {  
           T result = function(); 
           tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
   }); 
   return tcs.Task; 
}

ここから そして ここから

タスクでこのようなパラダイムをサポートするには、タスクファサードと任意の非同期操作をタスクとして参照する機能を保持しながら、その基盤となるインフラストラクチャのルールに従ってそのタスクの有効期間を制御する方法が必要です。非同期で、それほどコストがかからないようにします。これがTaskCompletionSourceの目的です。

私が見たのは、.NETのソースでも使われています。 WebClient.cs

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

最後に、私は以下のものも役に立ちました:

私はいつもこの質問をします。つまり、外部リソースへのI/O呼び出しをブロックしているスレッドがどこかにあるはずです。非同期コードはリクエストスレッドを解放しますが、システム内の他の場所にある別のスレッドを犠牲にしてのみです。いいえ、まったく違います。非同期要求が拡大する理由を理解するために、非同期I/O呼び出しの(単純化された)例をトレースします。要求はファイルに書き込む必要があるとしましょう。要求スレッドは非同期書き込みメソッドを呼び出します。 WriteAsyncは基本クラスライブラリ(BCL)によって実装され、非同期I/Oに完了ポートを使用します。そのため、WriteAsync呼び出しは非同期ファイル書き込みとしてOSに渡されます。その後、OSはドライバスタックと通信し、データを渡してI/O要求パケット(IRP)に書き込みます。これが面白いところです。デバイスドライバがIRPをすぐに処理できない場合は、非同期で処理する必要があります。そのため、ドライバはディスクに書き込みを開始するように指示し、「保留中」の応答をOSに返します。 OSはその「保留中」の応答をBCLに渡し、BCLは不完全なタスクを要求処理コードに返します。要求処理コードはタスクを待ちます。タスクはそのメソッドから不完全なタスクを返します。最後に、要求処理コードは不完全なタスクをASP.NETに返すことになり、要求スレッドはスレッドプールに戻るために解放されます。

ASP.NETでのAsync/Await入門

目標が(応答性ではなく)スケーラビリティを向上させることである場合、それはすべてそれを実行する機会を提供する外部I/Oの存在に依存します。

10
Alberto