如何获得这个递归异步/等待问题的权利? [英] How to get this recursive async/await issue right?

查看:97
本文介绍了如何获得这个递归异步/等待问题的权利?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有访问每个节点的树状分层结构,并触发回调为每个节点,就像一个递归方法(code下面没有进行测试,并为例):

I have a recursive method that visits every node in a tree hierarchy and triggers a callback for every node, like (code below is not tested and an example):

void Visit(Node node, Func<Node, int> callback, CancellationToken ct)
{
  if(ct.IsCancellationRequested)
  {
    return;
  }

  var processedNode = DoSomeProcessing(node);

  int result = callback(processedNode);

  // Do something important with the returned int.
  ...


  // Recursion.
  foreach(var childNode in node.Children)
  {
    Visit(childNode, callback);
  }
}

上面的方法是从传回调异步方法调用。由于它是长期运行的,我换电话成 Task.Run()

async Task ProcessAsync(Func<Node, int> callback)
{
  var rootNode = await someWebService.GetNodes();
  await Task.Run( () => Visit(rootNode, callback) );
}

现在,这里的问题:一些异步code正在等待ProcessAsync():

Now here's the issue: some async code is awaiting ProcessAsync():

...
await ProcessAsync( node => {
  return DoSomethingWithTheNode();
});
...

这工作。然而,没有我们重构和 DoSomethingWithTheNode 变成异步: DoSomethingWithTheNodeAsync 。其结果将不建,因为委托类型不匹配。我不得不返回任务&LT; INT&GT; 而不是 INT

This works. However, no we're refactoring and DoSomethingWithTheNode becomes async: DoSomethingWithTheNodeAsync. The result won't build because the delegate types don't match. I would have to return Task<int> instead of int:

...

    await ProcessAsync( async node => {
      return await DoSomethingWithTheNodeAsync();
    });
    ...

如果我更改委托的签名 Func键&LT;节点,任务&LT; INT&GT;&GT; 我必须让我的访问方法异步这是某种奇怪的 - 它已经在运行一个工作,我不觉得使用获得通过异步回调递归异步方法非常好英寸无法解释,但看起来是错误的。

If I change the delegate's signature to Func<Node, Task<int>> I will have to make my Visit method async which is somehow weird - it is already running as a Task and I don't feel very good about using a recursive async method that gets an async callback passed in. Can't explain, but it looks wrong.

问:


  • 是方法只是不适合异步的世界?

  • 如何完成这件事吧?

  • 本来我不想用一个回调方法,但一个的IEnumerable&LT;节点&GT; 。然而,这似乎是不可能的异步方法。

  • 这是问题也许更多足够codereview.stackexchange.com?

  • Is the approach just not suitable for an async world?
  • How to get this done right?
  • I originally did not want to use a callback method but an IEnumerable<Node>. However this seems to be impossible with async methods.
  • Is this question maybe more adequate for codereview.stackexchange.com?

推荐答案

再保后,新的异步访问可能是这样的:

After re-factoring, your new async Visit might look like this:

async Task Visit(Node node, Func<Node, Task<int>> callback, CancellationToken ct)
{
  if(ct.IsCancellationRequested)
  {
    return;
  }

  var processedNode = DoSomeProcessing(node);

  int result = await callback(processedNode).ConfigureAwait(false);

  // Do something important with the returned int.
  ...


  // Recursion.
  foreach(var childNode in node.Children)
  {
    await Visit(childNode, callback, token);
  }
}

然后 ProcessAsync 是这样的:

async Task ProcessAsync(Func<Node, Task<int>> callback, token)
{
  var rootNode = await someWebService.GetNodes();
  await Visit(rootNode, callback, token);
}

和它可以简单地被称为是这样的:

And it can simply be called like this:

await ProcessAsync(DoSomethingWithTheNodeAsync, token);

因为你引入到异步回调,则很可能不再需要卸载 ProcessAsync 来一个单独的线程。下面我会试图解释为什么。

Because you are introducing asynchrony into your callback, most likely you no longer need to offload ProcessAsync to a separate thread. Below I'll try to explain why.

让我们考虑一分钟,你的 DoSomethingWithTheNodeAsync 是这样的:

Let's consider for a minute your DoSomethingWithTheNodeAsync looks like this:

async Task<int> DoSomethingWithTheNodeAsync(Node node)
{
    Debug.Print(node.ToString());
    await Task.Delay(10); // simulate an IO-bound operation
    return 42;
}

访问,在执行等待回调(processedNode).ConfigureAwait(假)将继续随机池中的线程(即发生服务异步 Task.Delay 操作完成的线程)。这样,UI线程将不再被阻止。

Inside Visit, the execution after await callback(processedNode).ConfigureAwait(false) will continue on a random pool thread (the thread which happened to serve the completion of the asynchronous Task.Delay operation). So, the UI thread will no longer be blocked.

这同样适用于您可能使用在 DoSomethingWithTheNodeAsync 其他纯异步API真(和这是当初之所以重新分解,我相信)。

The same is true for any other pure asynchronous API which you may be using inside DoSomethingWithTheNodeAsync (and which was the original reason for re-factoring, I believe).

现在,我唯一关心的是这样的:

var processedNode = DoSomeProcessing(node);

一旦你叫 ProcessAsync(DoSomethingWithTheNodeAsync),上述 DoSomeProcessing 的第一调用将发生在同一个线程原来的呼叫。如果这是一个UI线程, DoSomeProcessing 可能会阻止用户界面进行一次,争取只要处理它里面去。

Once you've called ProcessAsync(DoSomethingWithTheNodeAsync), the very first invocation of the above DoSomeProcessing will happen on the same thread as the original call. If that's a UI thread, DoSomeProcessing might block the UI for one time, for as as long as the processing goes inside it.

如果这是一个问题,那么无论你调用 ProcessAsync 从UI线程,具有 Task.Run ,例如:

If that's a problem, then wherever you invoke ProcessAsync from the UI thread, wrap it with Task.Run, e.g:

void async button_click(object s, EventArgs e)
{
    await Task.Run(() => ProcessAsync(DoSomethingWithTheNodeAsync));
}

请注意,我们还是不要用 Task.Run ProcessAsync 的任何地方,所以会有没有多余的树的递归遍历期间线程切换。

Note, we still don't use Task.Run anywhere inside ProcessAsync, so there will be no redundant thread switching during the recursive traversal of the tree.

另外请注意,你做的不可以需要另一个异步/的await 添加到拉姆达象下面这样:

Also note, you do not need to add another async/await to the lambda like below:

await Task.Run(async () => await ProcessAsync(DoSomethingWithTheNodeAsync));

这会增加一些多余的编译器生成的状态机code。 Task.Run 有一个覆盖处理的lambda返回任务&LT; T&GT; 任务,解包用 Task.Unwrap 嵌套任务。更多关于这 rel=\"nofollow\">。

That would add some redundant compiler-generated state machine code. Task.Run has an override to deal with a lambda returning Task<T> or Task, which unwraps the nested task with Task.Unwrap. More about this here.

最后,如果里面的东西 DoSomeProcessing DoSomethingWithTheNodeAsync 更新UI,它已经到了UI线程上完成。随着MonoTouch的,它可以通过 SynchronizationContext.Post / 进行发送UI线程的的SynchronizationContext的

Finally, if something inside DoSomeProcessing or DoSomethingWithTheNodeAsync updates the UI, it has to been done on the UI thread. With Monotouch, it can be done via SynchronizationContext.Post/Send of the UI thread's SynchronizationContext.

这篇关于如何获得这个递归异步/等待问题的权利?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆