如何获得这个递归异步/等待问题的权利? [英] How to get this recursive async/await issue right?
问题描述
我有访问每个节点的树状分层结构,并触发回调为每个节点,就像一个递归方法(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 $ C把它包$ C>
,例如:
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屋!