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


196

私が読んだすべてのブログ投稿は、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、非同期メソッドを作成する最良の方法ですか、それとも別の方法で作成する必要がありますか?


22
メソッドの構成方法について一般的な質問をしています。すでに同期しているメソッドを非同期のメソッドに変換する際の開始点を知りたいだけです。
Khalid Abuhakmeh 2013

2
では、典型的な同期メソッドをするのでしょうか。また、なぜそれを非同期にしたいのですか?
Eric Lippert 2013

たくさんのファイルをバッチ処理して結果オブジェクトを返す必要があるとしましょう。
Khalid Abuhakmeh 2013

1
OK、それで:(1)高遅延操作とは何か:ファイルを取得する-ネットワークが遅いなどの理由で-または処理を行う-CPUに負荷がかかるためです。そして(2)そもそもなぜそれを非同期にしたいのかをまだ言っていない。ブロックしたくないUIスレッドはありますか?
Eric Lippert 2013

21
@EricLippert opによって与えられる例は非常に基本的なものであり、実際にそれほど複雑である必要はありません。
David B.15年

回答:


226

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イントロが役に立つかもしれません。


10
@Stephen:「非同期メソッドが他の非同期メソッドに依存している場合」-わかりましたが、そうでない場合はどうでしょうか。BeginInvokeを呼び出すコードをいくつかのコールバックコードでラップしようとするとどうなりますか?
リシボブ2013

1
@Ricibob:TaskFactory.FromAsyncラップに使用する必要がありますBeginInvoke。「コールバックコード」の意味がわからない。自分の質問をコードで自由に投稿してください。
Stephen Cleary 2013

@Stephen:ありがとう-はいTaskFactory.FromAsyncは私が探していたものです。
リシボブ2013

1
スティーブンはforループで、次のイテレーションがすぐに呼び出されTask.Delayますか、それともリターンの後に呼び出されますか?
jp2code

3
@ jp2code:awaitは「非同期待機」であるため、タスクによって返されるタスクがTask.Delay完了するまで、次の反復に進みません。
Stephen Cleary 2015年

11

メソッド内で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に不完全なタスクを返すことになり、要求スレッドは解放されてスレッドプールに戻ります。要求処理コードはタスクを待機し、そのメソッドから不完全なタスクを返します。最後に、要求処理コードはASP.NETに不完全なタスクを返すことになり、要求スレッドは解放されてスレッドプールに戻ります。

ASP.NETでの非同期/待機の概要

ターゲットがスケーラビリティ(応答性ではなく)の向上である場合、それはすべて、それを行う機会を提供する外部I / Oの存在に依存しています。


1
Task.Run実装の上にコメントが表示される場合は、たとえば、指定された作業をキューに入れてThreadPoolで実行し、その作業のタスクハンドルを返します。あなたは実際に同じことをします。Task.Runの方が優れていると確信しています。これにより、CancelationTokenが内部的に管理され、スケジューリングと実行オプションでいくつかの最適化が行われます。
unsafePtr 2017

ThreadPool.QueueUserWorkItemで何をしたかは、Task.Runで実行できますよね?
Razvan

-1

メソッドを非同期にする非常に簡単な方法の1つは、Task.Yield()メソッドを使用することです。MSDNが述べるように:

await Task.Yield();を使用できます。非同期メソッドでは、メソッドを強制的に非同期で完了させます。

メソッドの先頭に挿入すると、呼び出し元にすぐに戻り、残りのメソッドを別のスレッドで完了します。

private async Task<DateTime> CountToAsync(int num = 1000)
{
    await Task.Yield();
    for (int i = 0; i < num; i++)
    {
        Console.WriteLine("#{0}", i);
    }
    return DateTime.Now;
}

WinFormsアプリケーションでは、メソッドの残りの部分は同じスレッド(UIスレッド)で完了します。Task.Yield()あなたが返されることを確実にしたい場合のために、確かに便利ですTaskすぐに作成時に完了しません。
Theodor Zoulias
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.