【问题标题】:How I can write same code using Delegate/Callback?如何使用委托/回调编写相同的代码?
【发布时间】:2017-05-24 04:26:11
【问题描述】:

这是一个非常简单的例子,可以在互联网上找到Async/Await概念,我想知道这个小逻辑如何使用Begin/End Invoke来实现? 我需要做的就是使用两种方法(Async/Await 和委托)准备一个复杂的逻辑,所以我想从一个基本的工作流程开始。

int countCharacters()
    {

        int count = 0;
        using (StreamReader reader= new StreamReader("D:\\Data.txt"))
        {
            string content = reader.ReadToEnd();
            count = content.Length;
            Thread.Sleep(5000);
        }
            return count; 
    }
    private async void btnProcessFIle_Click(object sender, EventArgs e)
    {
        Task<int> task = new Task<int>(countCharacters);
        task.Start();
        int count = await task;
        lblCount.Text = "No. of characters in file=" +Environment.NewLine+ count.ToString();


    }

【问题讨论】:

  • 我很好奇。为什么要使用非常好的 async/await 代码并将其转换为使用 Begin/End Invoke?开始/结束调用是这样做的旧方法。一旦你开始使用 async/await,除了提交家庭作业之外,没有其他理由需要这样做。
  • 你试过什么?您具体遇到了什么问题?从字面上看,only Begin/EndInvoke() 不可能做你想做的事,因为它们不包括 await 所具有的在完成后将你带回正确线程的功能。您需要添加对Control.Invoke() 的调用来完成此操作,或者使用Progress&lt;T&gt;,或者使用SynchronizationContext,或者......好吧,正如你所看到的,你的问题太广泛了,除了没有包括一个很好的minimal reproducible example 帮助我们了解你真正需要帮助的地方。
  • @PeterDuniho 我所知道的是:当我在异步方法中使用此 KW 调用任何方法时,Await 是一个暂停点,该函数将在后台运行。我只想使用痛苦的结构 Begin/EndInvoke 映射相同的逻辑。因为 Delegates 是传统的异步编程方式,而 Async/Await 从 4.5 版开始就一直存在。
  • @bradgonesurfing- 先生,我同意你的观点,这是我需要向其他人(我的队友)展示的内容,因此我提出了这个问题。
  • @Eqra 问题的代码有问题。您不应该使用冷任务,只需使用 Task.Run。此外,如果异步方法已经可用,则不应使用 Task.Run。否则,你只会浪费一个线程。使用ReadToEndAsync 而不是ReadToEnd

标签: c# winforms delegates async-await


【解决方案1】:

这是一个非常简单的例子,可以在互联网上找到 Async/Await 概念

这是一个非常糟糕的例子。

它使用任务构造函数和Start,这是一个明确的禁忌(实际上没有个有效的用例可以做到这一点)。

它还在“异步示例”中的后台线程上同步使用文件系统。

如果您想要一个如何异步使用同步(例如,受 CPU 限制的)代码的示例,那么这是一个更好的示例,它几乎可以做同样的事情:

int countCharacters()
{
  Thread.Sleep(5000);
  return 13;
}

private async void btnProcessFIle_Click(object sender, EventArgs e)
{
  var count = await Task.Run(() => countCharacters());
  lblCount.Text = "No. of characters in file=" + count;
}

请注意,这是如何从 UI 线程异步调用 CPU 绑定代码的示例 - 它不是“异步概念”的示例。

我想知道如何使用 Begin/End Invoke 来实现这个小逻辑?

由于您的工作是在后台线程上同步工作,这实际上很容易; you can just use Delegate.BeginInvoke 而不是创建自己的 IAsyncResult(这是关于 APM 真正困难的部分 - 如果您确实需要实现它,请参阅 2007-03 issue of MSDN Magazine 中的“实现 CLR 异步编程模型”)。

但由于您可以只使用Delegate.BeginInvoke,因此非常简单:

private void btnProcessFIle_Click(object sender, EventArgs e)
{
  var ui = SynchronizationContext.Current;
  Func<int> d = countCharacters;
  d.BeginInvoke(CountCharactersCallback, ui);
}

private void CountCharactersCallback(IAsyncResult ar) 
{
  var d = (Func<int>) ((AsyncResult) ar).AsyncDelegate;
  var ui = (SynchronizationContext) ar.AsyncState;
  try
  {
    var count = d.EndInvoke(ar);
    ui.Post(CountCharactersComplete, count);
  }
  catch (Exception ex)
  {
    var edi = ExceptionDispatchInfo.Capture(ex);
    ui.Post(CountCharactersError, state);
  }
}

private void CountCharactersComplete(object state)
{
  var count = (int) state;
  lblCount.Text = "No. of characters in file=" + count;
}

private void CountCharactersError(object state)
{
  var edi = (ExceptionDispatchInfo)state;
  edi.Throw();
}

注意事项:

  • CountCharactersCallback 是一个“裸回调”。从CountCharactersCallback 传播的任何异常都表示一个灾难性错误。
  • 特别是,必须小心不要让来自EndInvoke 的异常传播到BeginInvoke 回调之外。这是一个常见的错误。
  • 我正在使用SynchronizationContext 同步回UI 线程。这与await 的行为相同。
  • 我使用ExceptionDispatchInfo 来跨线程保留异常堆栈跟踪(不需要包装器异常)。
  • CountCharactersError 只是在消息循环中直接引发异常。这与async void 的行为相同。

【讨论】:

  • 很好的答案,很高兴您考虑了我的问题-:)。我想问两件事,您提供的链接已损坏或无法下载,其次您能解释一下吗? 请注意,这是一个如何从 UI 线程异步调用 CPU 绑定代码的示例 - 它不是“异步概念”的示例。 或者您能否提供任何链接以使该语句易于理解对我来说。
  • 1) 您必须取消阻止文件(右键单击,在属性下)。 2) 原样例uses Task.Run for the wrong thing.
【解决方案2】:

Feature BeginInvoke 是访问非 UI 线程内的 UI 元素,因此您需要像这样更改代码:

    private async void btnProcessFIle_Click(object sender, EventArgs e)
{
    var task = Task.Run(() => {
     var count = countCharacters();
     lblCount.BeginInvoke((Action) (() =>
       {
         lblCount.Text = "No. of characters in file=" +Environment.NewLine+ 
            count.ToString();
       }));
    });

    await task;

}

【讨论】:

  • 我既不想使用 Task 也不想使用异步或等待。我想使用这个 Delegate、CallBack、BeginInvoke 和 EndInvoke 来实现。我真的要求太多了吗?
  • @Eqra - 任务和异步/等待是为实现 TAP 而创建的 - 如果你只想使用多线程 - 没问题(但为什么? - 在这种情况下,你的 btnProcessFIle_Click() 在等待时被“冻结”了任务结束) - 您可以在 msdn 中找到示例,在“任务类”定义 msdn.microsoft.com/en-us/library/…
  • 这不是 OP 所要求的。问题是不是如何更新 UI。就是如何使用APM模型,即BeginReadToEnd等
【解决方案3】:

没有异步/等待的代码。您不需要使用 BeginInvoke,因为您可以使用 TaskScheduler.FromCurrentSynchronizationContext() 捕获上下文

int countCharacters()
    {

        int count = 0;
        using (StreamReader reader= new StreamReader("D:\\Data.txt"))
        {
            string content = reader.ReadToEnd();
            count = content.Length;
            Thread.Sleep(5000);
        }
            return count; 
    }


    private  void btnProcessFIle_Click(object sender, EventArgs e)
    {
        Task<int> task = new Task<int>(countCharacters);
        task.Start();
        var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        task.ContinueWith(count =>{

                 lblCount.Text = "No. of characters in file=" +Environment.NewLine+ count.ToString();   
        }, uiScheduler);
    }

如果你真的想使用 BeginInvoke 我猜你可以写

    private  void btnProcessFIle_Click(object sender, EventArgs e)
    {
        Task<int> task = new Task<int>(countCharacters);
        task.Start();
        var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        task.ContinueWith(count =>{
                 lblCount.BeginInvoke(()=>{
                     lblCount.Text = "No. of characters in file=" +Environment.NewLine+ count.ToString();
                 });

        });
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-16
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-26
    • 1970-01-01
    相关资源
    最近更新 更多