什么是新的C#异步功能的非网络示例?

Jay*_*uzi 20 c# async-await c#-5.0

微软刚刚宣布了新的C#Async功能.到目前为止,我见过的每个例子都是关于从HTTP异步下载的东西.当然还有其他重要的异步事物吗?

假设我没有编写新的RSS客户端或Twitter应用程序.C#Async对我有什么好玩的?

编辑我有一个啊哈!看安德斯的PDC会议的那一刻.在过去,我一直致力于使用"观察者"线程的程序.这些线程正在等待某些事情发生,比如观察要更改的文件.他们没有做工作,他们只是空闲,并在发生事情时通知主线程.这些线程可以在新模型中替换为await/async代码.

Jef*_*ado 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);
}
Run Code Online (Sandbox Code Playgroud)

好的,我们已经实施了.但是等一下,计算需要几分钟才能完成.如果我们想要在进行计算时使用交互式应用程序并执行其他操作(例如渲染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);
}
Run Code Online (Sandbox Code Playgroud)
  • 注意:当然我们可以启动另一个线程来执行此操作,但这意味着我们正在生成一个只是坐在那里等待解除阻塞的线程,然后做一些有用的工作.那将是一种浪费.

现在调用是异步的,我们不必担心等待计算完成和处理,它是异步完成的.它会尽可能完成.直接异步调用代码的替代方法,您可以使用任务:

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();
}
Run Code Online (Sandbox Code Playgroud)

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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

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


白皮书中使用的另一个实际示例是在UI应用程序上使用它.修改为使用上面的例子:

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

如果您已经完成了任何UI编程(无论是WinForms还是WPF)并试图在处理程序中调用昂贵的函数,您就会知道这很方便.使用后台工作程序对此没有多大帮助,因为后台线程将坐在那里等待它可以工作.


假设你有办法控制一些外部设备,让我们说一台打印机.并且您希望在发生故障后重新启动设备.当然,打印机启动并准备好运行需要一些时间.您可能必须考虑重启没有帮助并尝试重新启动.你别无选择,只能等待它.如果你是异步地做的话.

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

        printer = await printer.WaitUntilReadyAsync();

    } while (printer.HasFailed);
}
Run Code Online (Sandbox Code Playgroud)

想象一下,在没有异步的情况下编写循环.


我的最后一个例子.想象一下,如果你必须在一个函数中做多个阻塞操作并想要异步调用.你更喜欢什么?

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();
}
Run Code Online (Sandbox Code Playgroud)

阅读白皮书,它实际上有许多实际的例子,如编写并行任务和其他.

我迫不及待地想要在CTP中开始玩这个游戏,或者当.NET 5.0最终解决这个问题时.

  • 错误.`async`不会启动任何线程,所以你的DoSomeVeryExpensiveCalculation()不会在后台执行.请记住,`async`适用于仅运行一小段时间然后`等待'的操作.如果要在后台运行长时间的CPU密集型操作,则需要在后台显式执行此操作:static Task <object> DoSomeVeryExpensiveCalculationAsync(object args){return Task.CreateNew(()=> DoSomeVeryExpensiveCalculation(args)); } (6认同)

Eri*_*ert 17

主要场景是涉及高延迟的任何场景.也就是说,"要求结果"和"获得结果"之间有很多时间.网络请求是高延迟场景的最明显的例子,一般是I/O,然后是在另一个核心上受CPU限制的冗长计算.

但是,这种技术可能与其他方案很好地融合在一起.例如,考虑编写FPS游戏逻辑的脚本.假设您有一个按钮单击事件处理程序.当玩家点击按钮时你想要发出警报两秒钟以警告敌人,然后打开门十秒钟.说出类似的话会不会很好:

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();
Run Code Online (Sandbox Code Playgroud)

每个任务都在UI线程上排队,因此没有任何阻塞,每个任务在作业完成后在正确的点恢复点击处理程序.


Dan*_*iel 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,这并不比简单更简单

toolWindow.Closed += delegate { toolWindow = null; }
Run Code Online (Sandbox Code Playgroud)

但我认为它很好地展示了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);
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,您可以使用普通的事件处理程序执行此操作,但它需要您拆分循环并将其转换为更难理解的内容.

请记住,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,似乎不可能GetAwaiter()直接将方法添加到EventHandler(允许await button.Click;),因为那时该方法不知道如何注册/注销该事件.它有点样板,但AwaitableEvent类可以重用于所有事件(不仅仅是UI).通过一些小修改并添加一些泛型,您可以允许检索EventArgs:

MouseEventArgs e = await button.OnMouseDown();
Run Code Online (Sandbox Code Playgroud)

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