20

Microsoft は、新しい C# Async 機能を発表しました。これまで見てきたすべての例は、HTTP から何かを非同期的にダウンロードすることに関するものでした。確かに他の重要な非同期のものがありますか?

新しい RSS クライアントや Twitter アプリを書いているわけではないとします。私にとって C# Async の興味深い点は何ですか?

編集私はAhaを持っていました!Anders の PDC セッションを見ている瞬間。過去に、「ウォッチャー」スレッドを使用するプログラムに取り組んできました。これらのスレッドは、ファイルの変更を監視するなど、何かが起こるのを待っています。彼らは仕事をしておらず、アイドル状態であり、何かが起こるとメインスレッドに通知します。これらのスレッドは、新しいモデルで await/async コードに置き換えることができます。

4

8 に答える 8

25

おお、これは面白そうですね。私はまだ CTP をいじっているのではなく、ホワイトペーパーを確認しているだけです。それについての Anders Hejlsberg の講演を見た後、私はそれがどのように有用であるかを理解できると思います。

私が理解しているように、async を使用すると、非同期呼び出しの記述が読みやすく実装しやすくなります。これとほぼ同じように、イテレータを記述することは今の方が簡単です (機能を手で書き出すのとは対照的です)。ブロックが解除されるまで、有用な作業を行うことができないため、これは必須のブロック プロセスです。ファイルをダウンロードしていた場合、そのファイルを取得してスレッドを無駄にするまでは、何の役にも立ちません。未定の長さでブロックし、何らかの結果を返すことがわかっている関数を呼び出して、それを処理する方法を考えてみてください (たとえば、結果をファイルに保存します)。あなたはそれをどのように書きますか?簡単な例を次に示します。

static object DoSomeBlockingOperation(object args)
{
    // block for 5 minutes
    Thread.Sleep(5 * 60 * 1000);

    return args;
}

static void ProcessTheResult(object result)
{
    Console.WriteLine(result);
}

static void CalculateAndProcess(object args)
{
    // let's calculate! (synchronously)
    object result = DoSomeBlockingOperation(args);

    // let's process!
    ProcessTheResult(result);
}

わかりました、実装しました。しかし、待ってください。計算が完了するまでに数分かかります。インタラクティブなアプリケーションが必要で、計算が行われている間 (UI のレンダリングなど) 他のことをしたい場合はどうすればよいでしょうか? 関数を同期的に呼び出し、スレッドがブロック解除されるのを待っているため、関数がアプリケーションを効果的にフリーズするのを待つ必要があるため、これは良くありません。

答え、関数の高価な関数を非同期で呼び出します。そうすれば、ブロッキング操作が完了するのを待つ必要がなくなります。しかし、どうやってそれを行うのですか?関数を非同期で呼び出し、ブロックが解除されたときに呼び出されるコールバック関数を登録して、結果を処理できるようにします。

static void CalculateAndProcessAsyncOld(object args)
{
    // obtain a delegate to call asynchronously
    Func<object, object> calculate = DoSomeBlockingOperation;

    // define the callback when the call completes so we can process afterwards
    AsyncCallback cb = ar =>
        {
            Func<object, object> calc = (Func<object, object>)ar.AsyncState;
            object result = calc.EndInvoke(ar);

            // let's process!
            ProcessTheResult(result);
        };

    // let's calculate! (asynchronously)
    calculate.BeginInvoke(args, cb, calculate);
}
  • 注: もちろん、これを行うために別のスレッドを開始することもできますが、それは、ブロックが解除されるのを待っているだけのスレッドを生成してから、いくつかの有用な作業を行うことを意味します。それはもったいないでしょう。

これで、呼び出しは非同期になり、計算が完了して処理されるのを待つ必要がなくなりました。非同期で行われます。できたら終わります。コードを非同期で直接呼び出す代わりに、Task を使用できます。

static void CalculateAndProcessAsyncTask(object args)
{
    // create a task
    Task<object> task = new Task<object>(DoSomeBlockingOperation, args);

    // define the callback when the call completes so we can process afterwards
    task.ContinueWith(t =>
        {
            // let's process!
            ProcessTheResult(t.Result);
        });

    // let's calculate! (asynchronously)
    task.Start();
}

これで、関数を非同期で呼び出しました。しかし、そのようになるのに何が必要でしたか?まず、デリゲート/タスクを非同期で呼び出すことができるようにする必要がありました。結果を処理してから関数を呼び出すことができるコールバック関数が必要でした。非同期で何かを呼び出すためだけに、2 行の関数呼び出しをさらに多くの機能に変更しました。それだけでなく、コード内のロジックは以前よりも複雑になっています。タスクを使用することでプロセスが簡素化されましたが、それを実現するために何かを行う必要がありました。非同期で実行して結果を処理したいだけです。なぜそれができないのですか?さて、次のことができます。

// need to have an asynchronous version
static async Task<object> DoSomeBlockingOperationAsync(object args)
{
    //it is my understanding that async will take this method and convert it to a task automatically
    return DoSomeBlockingOperation(args);
}

static async void CalculateAndProcessAsyncNew(object args)
{
    // let's calculate! (asynchronously)
    object result = await DoSomeBlockingOperationAsync(args);

    // let's process!
    ProcessTheResult(result);
}

これは、単純な操作 (計算、処理) を使用した非常に単純化された例です。各操作を便利に個別の関数に入れることができず、代わりに数百行のコードがあると想像してみてください。これは、非同期呼び出しの利点を得るために、さらに複雑になります。


ホワイトペーパーで使用されているもう 1 つの実用的な例は、UI アプリでの使用です。上記の例を使用するように変更します。

private async void doCalculation_Click(object sender, RoutedEventArgs e) {
    doCalculation.IsEnabled = false;
    await DoSomeBlockingOperationAsync(GetArgs());
    doCalculation.IsEnabled = true;
}

UI プログラミング (WinForms であれ WPF であれ) を行ったことがあり、ハンドラー内で高価な関数を呼び出そうとしたことがあれば、これが便利であることがわかるでしょう。これにバックグラウンド ワーカーを使用しても、バックグラウンド スレッドが機能するまで待機しているため、あまり役に立ちません。


プリンターなどの外部デバイスを制御する方法があるとします。また、障害後にデバイスを再起動したいと考えていました。当然、プリンターが起動して操作できるようになるまでには時間がかかります。再起動が役に立たないことを考慮して、再起動を試みる必要がある場合があります。それを待つしかありません。非同期で行った場合ではありません。

static async void RestartPrinter()
{
    Printer printer = GetPrinter();
    do
    {
        printer.Restart();

        printer = await printer.WaitUntilReadyAsync();

    } while (printer.HasFailed);
}

非同期なしでループを書くことを想像してみてください。


私が持っている最後の例。関数で複数のブロック操作を行う必要があり、非同期で呼び出したい場合を想像してください。何がいいですか?

static void DoOperationsAsyncOld()
{
    Task op1 = new Task(DoOperation1Async);
    op1.ContinueWith(t1 =>
    {
        Task op2 = new Task(DoOperation2Async);
        op2.ContinueWith(t2 =>
        {
            Task op3 = new Task(DoOperation3Async);
            op3.ContinueWith(t3 =>
            {
                DoQuickOperation();
            }
            op3.Start();
        }
        op2.Start();
    }
    op1.Start();
}

static async void DoOperationsAsyncNew()
{
    await DoOperation1Async();

    await DoOperation2Async();

    await DoOperation3Async();

    DoQuickOperation();
}

ホワイトペーパーを読んでください。実際には、並列タスクの作成などの実用的な例がたくさんあります。

CTP で、または .NET 5.0 が最終的に完成したときに、これを試してみるのが待ちきれません。

于 2010-10-28T23:50:35.467 に答える
17

主なシナリオは、待ち時間が長いシナリオです。つまり、「結果を求める」と「結果を得る」の間に多くの時間がかかります。ネットワーク要求は高レイテンシ シナリオの最も明白な例であり、一般的な I/O がそれに続き、CPU が別のコアにバインドされている長時間の計算がそれに続きます。

ただし、このテクノロジがうまくかみ合う他のシナリオが潜在的に存在します。たとえば、FPS ゲームのロジックをスクリプト化するとします。ボタン クリック イベント ハンドラーがあるとします。プレイヤーがボタンをクリックすると、サイレンを 2 秒間鳴らして敵に警告し、ドアを 10 秒間開きます。次のように言えばよろしいでしょうか。

button.Disable();
await siren.Activate(); 
await Delay(2000);
await siren.Deactivate();
await door.Open();
await Delay(10000);
await door.Close();
await Delay(1000);
button.Enable();

各タスクは UI スレッドでキューに入れられるため、何もブロックされず、それぞれのタスクがジョブの終了後に適切な時点でクリック ハンドラーを再開します。

于 2010-10-31T15:15:05.727 に答える
9

今日、これに関する別の優れた使用例を見つけました。ユーザーの操作を待つことができます。

たとえば、あるフォームに別のフォームを開くボタンがある場合:

Form toolWindow;
async void button_Click(object sender, EventArgs e) {
  if (toolWindow != null) {
     toolWindow.Focus();
  } else {
     toolWindow = new Form();
     toolWindow.Show();
     await toolWindow.OnClosed();
     toolWindow = null;
  }
}

確かに、これは実際にはそれほど単純ではありません

toolWindow.Closed += delegate { toolWindow = null; }

しかし、何ができるかをうまく示していると思いawaitます。そして、イベント ハンドラーのコードが自明ではawaitなくなったら、プログラミングがはるかに簡単になります。ユーザーが一連のボタンをクリックしなければならないことを考えてみてください。

async void ButtonSeries()
{
  for (int i = 0; i < 10; i++) {
    Button b = new Button();
    b.Text = i.ToString();
    this.Controls.Add(b);
    await b.OnClick();
    this.Controls.Remove(b);
  }
}

確かに、これは通常のイベント ハンドラーで行うことができますが、ループを分解して、はるかに理解しにくいものに変換する必要があります。

awaitこれは、将来のある時点で完了するものすべてに使用できることを忘れないでください。上記を機能させるための拡張メソッド Button.OnClick() を次に示します。

public static AwaitableEvent OnClick(this Button button)
{
    return new AwaitableEvent(h => button.Click += h, h => button.Click -= h);
}
sealed class AwaitableEvent
{
    Action<EventHandler> register, deregister;
    public AwaitableEvent(Action<EventHandler> register, Action<EventHandler> deregister)
    {
        this.register = register;
        this.deregister = deregister;
    }
    public EventAwaiter GetAwaiter()
    {
        return new EventAwaiter(this);
    }
}
sealed class EventAwaiter
{
    AwaitableEvent e;
    public EventAwaiter(AwaitableEvent e) { this.e = e; }

    Action callback;

    public bool BeginAwait(Action callback)
    {
        this.callback = callback;
        e.register(Handler);
        return true;
    }
    public void Handler(object sender, EventArgs e)
    {
        callback();
    }
    public void EndAwait()
    {
        e.deregister(Handler);
    }
}

残念ながら、メソッドを(許可して)GetAwaiter()に直接追加することはできないようです。これは、メソッドがそのイベントを登録/登録解除する方法がわからないためです。これは定型文ですが、AwaitableEvent クラスは (UI だけでなく) すべてのイベントで再利用できます。マイナーな変更といくつかのジェネリックを追加すると、EventArgs を取得できるようになります。EventHandlerawait button.Click;

MouseEventArgs e = await button.OnMouseDown();

これは、より複雑な UI ジェスチャ (ドラッグ アンド ドロップ、マウス ジェスチャなど) で役立つことがわかりましたが、現在のジェスチャをキャンセルするためのサポートを追加する必要があります。

于 2010-10-30T11:39:15.590 に答える
4

CTP には、ネットを使用しないサンプルとデモがいくつかあり、I/O をまったく使用しないものもあります。

そして、それはすべてのマルチスレッド/並列の問題領域 (既に存在する) に適用されます。

Async と Await は、CPU バウンドであろうと I/O バウンドであろうと、すべての並列コードを構造化する新しい (より簡単な) 方法です。最大の改善点は、C#5 より前は APM (IAsyncResult) モデルまたはイベント モデル (BackgroundWorker、WebClient) を使用する必要があった領域です。だからこそ、それらの例が今パレードをリードしていると思います。

于 2010-10-30T18:59:39.223 に答える
3

GUI クロックが良い例です。表示される時間を毎秒更新する時計を描きたいとします。概念的に、あなたは書きたい

 while true do
    sleep for 1 second
    display the new time on the clock

また、await(または F# async を使用して) 非同期的にスリープする場合は、このコードを記述して、UI スレッドでノンブロッキング方式で実行できます。

http://lorgonblog.wordpress.com/2010/03/27/f-async-on-the-client-side/

于 2010-10-31T20:25:19.733 に答える
2

拡張機能は、非同期操作asyncを行う場合に役立ちます。非同期操作には明確な開始完了があります。非同期操作が完了すると、結果またはエラーが発生する場合があります。(キャンセルは特別な種類のエラーとして扱われます)。

非同期操作は、次の 3 つの状況で役立ちます (大まかに言えば)。

  • UI の応答性を維持します。長時間実行される操作 (CPU バウンドまたは I/O バウンド) がある場合は常に、非同期にします。
  • サーバーのスケーリング。サーバー側で非同期操作を慎重に使用すると、サーバーのスケーリングに役立つ場合があります。たとえば、非同期 ASP.NET ページはasync操作を利用する場合があります。ただし、これが常に成功するとは限りません。最初にスケーラビリティのボトルネックを評価する必要があります。
  • ライブラリまたは共有コードでクリーンな非同期 API を提供する。async再利用性に優れています。

物事を行う方法を採用し始めるとasync、3 番目の状況がより一般的になっていることがわかります。asyncコードは他のコードと最適に機能するasyncため、非同期コードの種類はコードベースを通じて「成長」します。

最適なツールでasyncない同時実行の種類がいくつかあります。

  • 並列化。並列アルゴリズムは、多くのコア (CPU、GPU、コンピューター) を使用して問題をより迅速に解決する場合があります。
  • 非同期イベント。非同期イベントは、プログラムとは関係なく常に発生します。多くの場合、「完了」はありません。通常、プログラムは非同期イベント ストリームにサブスクライブし、いくつかの更新を受信して​​から、サブスクライブを解除します。プログラムは、サブスクライブサブスクライブ解除を「開始」と「完了」として扱うことができますが、実際のイベント ストリームが実際に停止することはありません。

並列操作は、PLINQ または を使用して表現するのが最適ですParallel。これらには、パーティショニング、制限された同時実行などのサポートが多数組み込まれているためです。並列操作は、ThreadPoolスレッドから実行することにより、awaitable に簡単にラップできます ( Task.Factory.StartNew)。

非同期イベントは、非同期操作に適切にマップされません。問題の 1 つは、非同期操作の完了時点で結果が 1 つしかないことです。非同期イベントには、任意の数の更新が含まれる場合があります。Rxは、非同期イベントを処理するための自然言語です。

Rx イベント ストリームから非同期操作へのマッピングはいくつかありますが、すべての状況に適しているわけではありません。Rx で非同期操作を使用する方が、その逆よりも自然です。IMO、これにアプローチする最善の方法は、ライブラリと低レベルのコードで可能な限り非同期操作を使用することです。ある時点で Rx が必要な場合は、そこから Rx を使用します。

于 2011-08-30T14:52:21.370 に答える
0

これはおそらく、新しい非同期機能 (新しい RSS クライアントや Twitter アプリを作成していない) を使用しない方法の良い例であり、仮想メソッド呼び出しの中間メソッド オーバーロード ポイントです。正直なところ、メソッドごとに複数のオーバーロード ポイントを作成する方法があるかどうかはわかりません。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace AsyncText
{
    class Program
    {
        static void Main(string[] args)
        {
            Derived d = new Derived();

            TaskEx.Run(() => d.DoStuff()).Wait();

            System.Console.Read();
        }
        public class Base
        {
            protected string SomeData { get; set; }

            protected async Task DeferProcessing()
            {
                await TaskEx.Run(() => Thread.Sleep(1) );
                return;
            }
            public async virtual Task DoStuff() {
                Console.WriteLine("Begin Base");
                Console.WriteLine(SomeData);
                await DeferProcessing();
                Console.WriteLine("End Base");
                Console.WriteLine(SomeData);
            }
        }
        public class Derived : Base
        {
            public async override Task DoStuff()
            {
                Console.WriteLine("Begin Derived");
                SomeData = "Hello";
                var x = base.DoStuff();
                SomeData = "World";
                Console.WriteLine("Mid 1 Derived");
                await x;
                Console.WriteLine("EndDerived");
            }
        }
    }
}

出力は次のとおりです。

派生開始

開始ベース

こんにちは

ミッド 1 派生

エンドベース

世界

EndDerived

特定の継承階層 (つまり、コマンド パターンを使用) を使用すると、時々このようなことをしたいと思うことがあります。

于 2011-05-23T10:10:38.317 に答える
0

これは、UI と複数のアクションを含むネットワーク化されていないシナリオで「非同期」構文を使用する方法を示す記事です。

于 2011-11-21T16:23:21.477 に答える