2010-10-28 27 views
20

微软刚刚宣布new C# Async feature。我目前看到的每个例子都是关于从HTTP异步下载的东西。当然还有其他重要的异步事物?什么是新的C#异步功能的非联网示例?

假设我没有写一个新的RSS客户端或Twitter应用程序。对我而言,C#Async有什么有趣的地方?

编辑我有一个啊哈!一边看Anders' PDC session。在过去,我一直在使用“观察者”线程的程序。这些线程正在等待发生某些事情,比如看一个要更改的文件。他们没有工作,他们只是闲置,并在事情发生时通知主线程。这些线程可以替换为新模型中的await/async代码。

+0

或者更一般地提出这个问题,何时async有用?几乎任何一般的异步示例都应该与C#5中显示的内容相关。 – Larsenal 2010-10-28 23:05:43

+0

当您说“非联网”时,您是否确实指“非I/O”?因为实际上所有的I/O都可能阻塞。 – Gabe 2010-10-29 03:10:25

+0

+1假设我正在处理来自网络的数据流,但是通过3D方终端进行处理,我不知道终端内部或终端是如何从网络获取数据,我只是使用它的dll并获取所有数据在我的程序中异步显示为魔法。不需要知道关于网络的任何信息。但我需要知道异步/等待 – 2013-04-08 09:58:30

回答

25

哦,这听起来很有趣。我还没有玩CTP,只是审查了白皮书。在看到Anders Hejlsberg's talk之后,我想我可以看到它是如何有用的。

据我所知,异步使编写异步调用更容易阅读和实现。非常类似于现在编写迭代器更容易(而不是用手写出功能)。这是基本的阻止流程,因为在解除阻止之前,无法进行有用的工作。如果你正在下载一个文件,你不能做任何有用的事情,直到你得到该文件让线程浪费。考虑如何调用一个你知道会阻塞一个不确定长度并返回一些结果然后处理它的函数(例如,将结果存储在一个文件中)。你会怎么写?这里有一个简单的例子:

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); 
} 
  • 注:当然,我们可以启动另一个线程来做到这一点,但是这将意味着我们正在产卵一个线程,只是坐在那里等着畅通,然后做一些有益的工作。这将是一种浪费。

现在调用是异步的,我们不必担心等待计算完成和处理,它是异步完成的。它会在完成时完成。除了直接调用代码之外,您可以使用任务:

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(); 
} 

现在我们异步调用函数。但是,为了达到这个目的,需要做些什么?首先,我们需要委托/任务能够异步调用它,我们需要一个回调函数来处理结果,然后调用函数。我们已经将两行函数调用变成了更多,只是为了异步调用某些东西。不仅如此,代码中的逻辑变得越来越复杂了。虽然使用任务有助于简化流程,但我们仍然需要做一些事情才能实现。我们只是想异步运行然后处理结果。为什么我们不能这样做?现在好了,我们可以:

// 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); 
} 

现在,这是通过简单的操作(计算,过程)非常简单的例子。想象一下,如果每个操作都不能方便地放入单独的函数中,而是有数百行代码。这只是为了获得异步调用的好处而增加了很多复杂性。


白皮书中使用的另一个实际示例是在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(); 
} 

阅读whitepaper,它实际上有很多实际的例子,比如编写并行任务等。

我迫不及待地想要在CTP或.NET 5.0最终实现时开始玩这个游戏。

+0

我不确定使用异步的语法是100%,但你明白了。 – 2010-10-29 00:48:13

+0

在UI的情况下,我认为'BackgroundWorker'会是使用'async'和'await'的更好选择。 – Brian 2010-10-29 17:26:10

+6

错了。 'async'不会启动任何线程,所以你的DoSomeVeryExpensiveCalculation()将不会在后台执行。请记住,“异步”适用于只运行很短时间然后“等待”某些操作。如果你想在后台运行长CPU密集型操作,你需要明确这样做的背景: 静态任务 DoSomeVeryExpensiveCalculationAsync(对象参数) { 回报Task.CreateNew(()=> DoSomeVeryExpensiveCalculation(参数) ); } – Daniel 2010-10-29 17:26:21

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()方法EventHandler(允许await button.Click;),因为这样的方法不知道如何注册/注销该事件。 这是一个样板,但AwaitableEvent类可以重新用于所有事件(不仅仅是UI)。并配有小的修改,并增加了一些仿制药,你可以让检索的EventArgs:

MouseEventArgs e = await button.OnMouseDown(); 

我可以看到这是一些更复杂的UI手势有用(拖放,鼠标手势,...) - 尽管您必须添加对取消当前手势的支持。

4

CTP中有一些示例和演示不使用网络,甚至有些不做任何I/O的示例和演示。

它适用于所有多线程/并行问题区域(已存在)。

异步和等待是一种新的(更容易)构造所有并行代码的方式,无论是CPU绑定还是I/O绑定。最大的改进之处在于,在C#5之前,您必须使用APM(IAsyncResult)模型或事件模型(BackgroundWorker,WebClient)。我认为这就是为什么这些例子现在引领游行。

17

主要场景是涉及高延迟的任何场景。也就是说,“请求结果”和“获得结果”之间有很多时间。网络请求是高延迟场景中最明显的例子,紧随其后的是一般的I/O,然后是在另一个核心上进行CPU绑定的冗长计算。

但是,这种技术可能会与其他场景很好地匹配。例如,考虑编写FPS游戏的逻辑。假设你有一个按钮点击事件处理程序。当玩家点击按钮时,您想要发出警报两秒钟以提醒敌人,然后打开门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线程上,所以没有什么块,它的工作完成后每一个在正确的点继续单击处理程序。

3

GUI时钟就是一个很好的例子;比如说你想绘制一个时钟,更新每秒显示的时间。从概念上讲,你想写

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

await(或F#异步)异步睡觉,你可以这样写代码在非阻塞时尚的UI线程上运行。

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

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

对于某些继承层次(即使用命令模式),我发现自己想要做这样的东西偶尔。

2

当您有异步操作时,async扩展名在某些情况下非常有用。异步操作有一个确定的开始完成。当异步操作完成时,它们可能有结果或错误。 (取消被视为一种特殊的错误)。

异步操作是在三种情况下非常有用(广义上):

  • 保持你的UI响应。任何时候你有一个长时间运行的操作(不论是CPU限制还是I/O限制),都要使其异步。
  • 扩展您的服务器。在服务器端明智地使用异步操作可以帮助您的服务器进行扩展。例如,异步ASP.NET页面可以使用async操作。但是,这是not always a win;,您需要首先评估您的可伸缩性瓶颈。
  • 在库或共享代码中提供干净的异步API。 async非常适合重复使用。

当你开始采用async的做法时,你会发现第三种情况越来越普遍。 async代码与其他async代码效果最好,因此异步代码类型通过代码库“增长”。

有一对夫妇的并发类型,其中async最好的工具:

  • 并行。并行算法可以使用许多内核(CPU,GPU,计算机)更快地解决问题。
  • 异步事件。异步事件始终发生,与您的程序无关。他们通常没有“完成”。通常,您的程序将订阅到一个异步事件流,接收一些更新,然后取消订阅。您的程序可以治疗订阅退订为“开始”和“完成”,但实际的事件流从来没有真正停止。

并行操作是使用PLINQ或Parallel,因为他们有很多的最佳表达的内置的分区,有限并发等的并行操作可以容易地被包裹在一个awaitable支持通过从ThreadPool运行它线程(Task.Factory.StartNew)。

异步事件不能很好地映射到异步操作。一个问题是异步操作在完成时有一个结果。异步事件可能有任何数量的更新。 Rx是处理异步事件的自然语言。

有从接收事件流的异步操作一些映射,但他们都不是理想的所有情况。 Rx使用异步操作更自然,而不是相反。 IMO,解决这个问题的最好方法是尽可能在库和底层代码中使用异步操作,如果您需要Rx,那么请使用Rx。

0

这里是一个article约展示了如何使用“异步”的语法在涉及用户界面和多个操作的非联网方案。