RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 780882
Accepted
iluxa1810
iluxa1810
Asked:2020-02-06 13:42:26 +0000 UTC2020-02-06 13:42:26 +0000 UTC 2020-02-06 13:42:26 +0000 UTC

处理事件时使用 async/await 的优缺点

  • 772

在这个线程中有一句话,根据 async / await 模型处理事件比传统的基于事件的方法有很多优势 - 订阅和忘记。

从理论上讲,您可以在没有 async/await 的情况下生活。但是随后您将代码分散在事件处理程序中,并且状态将采用全局变量的形式。而且我什至没有谈论异常处理——这将非常困难。但是,是的,不知何故,人们以前没有 async / await 生活,但仍然生活在其他语言中。

事实上,它让我感兴趣。

我想通过示例、传统方法和 async / await 方法获取有关所有优点和可能缺点的信息。

c#
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. tym32167
    2020-02-06T18:20:19Z2020-02-06T18:20:19Z

    假设我们有一个 UI 应用程序的任务:执行一些逻辑,然后显示一个视图,等到该视图关闭并执行其他操作。如何以传统方式解决它(我使用 Window 作为视图纯粹是为了简化示例,在实际任务中,任何东西都可以代替窗口):

    int i = 0;
    void EventBased()
    {
        i = DoSmthg();
        var wnd = new Wnd();
        wnd.Closed+=Closed;     
        wnd.Show();
    }
    
    void Closed(object sender, EventArgs e)
    {
        (sender as Wnd).Closed -= Closed;
        DoSmthgElse(i);
    }
    

    注意3点:

    1. 看似一种方法的逻辑分散在几种方法上。当然,这可以通过在现场直接将处理程序指定为匿名委托来解决,但也有缺点。
    2. EventBased() 方法是非阻塞的。也就是说,调用这个方法的人不会知道整个逻辑的结束。这也可以通过向调度程序添加一个框架来解决,或者如果 wnd 是一个窗口则通过 wnd.ShowDialog() 来解决(尽管这本质上也是添加一个框架),或者通过一个新事件来解决,这也有其缺点。
    3. 我们被迫将中间结果的值存储在 i 字段中。这也可以通过匿名委托绕过,但这仍然会导致变量捕获

    正如你所看到的,如果你想解决一个看似简单的问题,你必须躲避让代码按照你想要的方式工作。但是让我们编写一个更易于使用的窗口:

    public class Wnd : Window
    {
        TaskCompletionSource<object> s;
    
        public Wnd()
        {
            s = new TaskCompletionSource<object>();     
            this.Closed+= (sender, args) => s.SetResult(this);
        }
    
        public Task ShowAsync()
        {
            this.Show();
            return s.Task;
        }
    }   
    

    如您所见,窗口现在有一个返回任务的方法。只有当窗口关闭时,此任务才会结束。现在我们可以像这样重写调用代码:

    async Task AsyncBased()
    {
        var i = DoSmthg();
        var wnd = new Wnd();
        await wnd.ShowAsync();
        DoSmthgElse(i);
    }
    

    不再摆弄框架或代表或任何东西。

    1. 所有必要的逻辑都集中在一种方法中(我们知道这并不完全正确,但对于代码的读者来说这是正确的),
    2. 调用代码也可以等到逻辑结束而不诉诸黑魔法
    3. 中间状态看起来像一个常规的局部变量

    这只是您可以在没有 async/await 的情况下使用的简单示例之一,但是使用 async/await 会使代码更加简洁和易于理解。您可以将窗口替换为任何内容(从网络获取数据、写入数据库、任何异步调用),该窗口仅作为使用TaskCompletionSource.

    • 18
  2. Best Answer
    VladD
    2020-02-06T20:41:59Z2020-02-06T20:41:59Z

    看。传统的异步工作方式是使用回调。在执行此操作的每一点,您都await必须通过订阅异步代码的末尾来结束该方法。这样做时,您必须将状态保存在某个地方,也就是说,您必须手动携带局部变量。此外,循环和条件的逻辑也被证明分布在几段代码中。好吧,您将不得不手动划分为必要的部分。

    这是一个简单的异步代码示例:复制流。

    async Task CopyAsync(Stream source, Stream target, CancellationToken ct)
    {
        try
        {
            var buf = new byte[8192];
            while (true)
            {
                var actuallyRead = await source.ReadAsync(buf, 0, buf.Length, ct);
                if (actuallyRead == 0)
                    return;
                await target.WriteAsync(buf, 0, actuallyRead, ct);
            }
        }
        catch (OperationCanceledException) when (ct.IsCancellationRequested)
        {
            Debug.WriteLine("Cancelled");
        }
    }
    

    没有什么特别的。

    我们如何在不占用线程的情况下编写相同的功能而无需同步代码?我们有一个方法stream.BeginRead应该返回一个类型为 的对象IAsyncResult。让我们尝试以相同的方式对我们的功能进行建模。

    首先,我们需要在某个地方存储缓冲区以及工作线程。为此,我们需要一个类。让我们称之为StreamCopyWorker,记住工作的逻辑也将在其中。接下来,我们要定义IAsyncResult. 让我们将它声明为一个单独的类,因为它仍然是一个单独的实体。

    StreamCopyWorker必须有方法BeginCopyAsync和EndCopyAsync. 实施。原来是这样一条鳄鱼:

    internal class StreamCopyWorker
    {
        internal readonly IAsyncResult Result;
        Stream source;
        Stream target;
        CancellationToken ct;
        ManualResetEventSlim ev = new ManualResetEventSlim();
        AsyncCallback cb;
    
        public StreamCopyWorker(
            Stream source, Stream target, object state, CancellationToken ct,
            AsyncCallback cb)
        {
            this.source = source;
            this.target = target;
            this.ct = ct;
            this.cb = cb;
            this.Result = new StreamCopyAsyncResult()
            {
                AsyncState = state,
                AsyncWaitHandle = ev.WaitHandle,
                self = this
            };
        }
    
        byte[] buf = new byte[8192];
    
        internal void BeginAsync()
        {
            source.BeginRead(buf, 0, buf.Length, DoWrite, null);
        }
    
        internal void EndAsync(IAsyncResult ar)
        {
            ct.ThrowIfCancellationRequested();
        }
    
        void DoWrite(IAsyncResult ar)
        {
            int bytesRead = source.EndRead(ar);
            if (bytesRead == 0 || ct.IsCancellationRequested)
                Finish();
            else
                target.BeginWrite(buf, 0, bytesRead, DoRead, null);
        }
    
        void DoRead(IAsyncResult ar)
        {
            target.EndWrite(ar);
            if (ct.IsCancellationRequested)
                Finish();
            else
                BeginAsync();
        }
    
        void Finish()
        {
            ((StreamCopyAsyncResult)Result).IsCompleted = true;
            ev.Set();
            cb(Result);
        }
    
        internal class StreamCopyAsyncResult : IAsyncResult
        {
            public bool IsCompleted { get; internal set; }
            public WaitHandle AsyncWaitHandle { get; internal set; }
            public object AsyncState { get; internal set; }
            public bool CompletedSynchronously => false;
            internal StreamCopyWorker self { get; set; }
        }
    }
    

    好吧,调用辅助方法来隐藏类的创建:

    IAsyncResult BeginCopyAsync(Stream source, Stream target, object state, CancellationToken ct, AsyncCallback cb)
    {
        var worker = new StreamCopyWorker(source, target, state, ct);
        worker.BeginAsync();
        return worker.Result;
    }
    
    void EndCopyAsync(IAsyncResult ar)
    {
        var result = (StreamCopyWorker.StreamCopyAsyncResult)ar;
        var worker = result.self;
        worker.EndAsync(ar);
    }
    

    没有 async/await 对你来说仍然很容易吗?

    使用纯事件模型,意大利面条就更糟了。现在你可以通过闭包来拉取状态,这稍微简化了代码。但不要太多。我对纯事件有相同的任务,看起来像这样:

    var buf = new byte[8192];
    ResultCallback cb = (o, args) =>
    {
        if (args.IsCancelled)
            Debug.WriteLine("Cancelled");
    };
    ReadHandler rhandler = null;
    rhandler = (o, args) =>
    {
        source.ReadFinished -= rhandler;
    
        if (ct.IsCancelationRequested)
            cb?.Invoke(null, new ResultArgs(isCancelled: true));
        else
        {
            var readBytes = args.ReadBytes;
            if (readBytes == 0)
                cb?.Invoke(null, new ResultArgs(isCancelled: false));
            else
            {
                WriteHandler whandler = null;
                whandler = (o, args) =>
                {
                    target.WriteFinished -= whandler;
                    if (ct.IsCancelationRequested)
                        cb?.Invoke(null, new ResultArgs(isCancelled: true));
                    else
                    {
                        source.ReadFinished += rhandler;
                        source.ReadAsync(buf, 0, buf.Length);
                    }
                };
                target.WriteFinished += whandler;
                target.WriteAsync(buf, 0, readBytes);
            }
        }
    };
    source.ReadFinished += rhandler;
    source.ReadAsync(buf, 0, buf.Length);
    

    (加上ResultCallback, ReadHandler,WriteHandler等的定义ResultArgs)。你肯定在 Javascript 代码中看到过类似的,只是更大的死亡金字塔。

    你明白这段代码发生了什么吗?我走了。

    • 18

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5