这里发生了一些事情,所以我将首先发布一个简单的答案,让我们看看它是否足够。
我们将一个任务包装在一个任务中,这需要双重等待。否则,您只是等待内部任务到达其第一个返回点,该返回点将(可以)位于第一个await
.
让我们看看你的DelayExecute
方法更详细。让我从更改方法的返回类型开始,我们将看到这如何改变我们的观点。返回类型的更改只是一个说明。您正在返回一个Task
现在这是非通用的,实际上你正在返回一个Task<Task>
.
public Task<Task> DelayExecute(Action func)
{
AbortCurrentTask();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask = Task.Factory.StartNew(async () =>
{
var sw = Stopwatch.StartNew();
await Task.Delay(timeout, cancellationToken);
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
});
}
(请注意,这不会编译,因为currentTask
也是类型Task
,但是如果您阅读了问题的其余部分,这在很大程度上是无关紧要的)
好的,那么这里会发生什么?
让我们先解释一个更简单的例子,我在中测试了这个LINQPad http://linqpad.net:
async Task Main()
{
Console.WriteLine("before await startnew");
await Task.Factory.StartNew(async () =>
{
Console.WriteLine("before await delay");
await Task.Delay(500);
Console.WriteLine("after await delay");
});
Console.WriteLine("after await startnew");
}
执行此操作时,我得到以下输出:
before await startnew
before await delay
after await startnew
after await delay -- this comes roughly half a second after previous line
那么为什么会这样呢?
嗯,你的StartNew
返回一个Task<Task>
。这不是一个现在将等待内部任务完成的任务,它等待内部任务完成return,它首先会(可以)做await
.
因此,让我们通过对有趣的行进行编号,然后解释事情发生的顺序来查看完整的执行路径:
async Task Main()
{
Console.WriteLine("before await startnew"); 1
await Task.Factory.StartNew(async () => 2
{
Console.WriteLine("before await delay"); 3
await Task.Delay(500); 4
Console.WriteLine("after await delay"); 5
});
Console.WriteLine("after await startnew"); 6
}
1.第一个Console.WriteLine
执行
这里没什么神奇的
2. 我们启动一个新任务Task.Factory.StartNew
并等待它。
这里我们的main方法现在可以返回,它将返回一个任务,一旦内部任务完成,该任务将继续。
内部任务的工作是not执行all该委托中的代码。内部任务的工作是产生另一个任务.
这个很重要!
3. 内部任务现在开始执行
它本质上会执行委托中的所有代码,包括调用Task.Delay
,它返回一个Task
.
4. 内部任务首先到达await
由于这尚未完成(它只会在大约 500 毫秒后完成),因此该委托现在返回.
基本上,await <this task we got from Task.Delay>
语句将创建一个延续,然后返回。
这个很重要!
6. 我们的外部任务现在继续
由于内部任务返回,外部任务现在可以继续。调用的结果await Task.Factory.StartNew
又是另一项任务,但是这项任务只能靠自己解决了.
5. 内部任务,大约持续500ms后
现在是在外部任务已经继续执行之后,可能已经完成执行。
所以总而言之,你的代码“按预期”执行,只是不是这样you预期的。
有多种方法可以修复此代码,我只是重写您的DelayExecute
方法以最简单的方式做你想做的事:
更改这一行:
return currentTask = Task.Factory.StartNew(async () =>
进入这个:
return currentTask = await Task.Factory.StartNew(async () =>
这将使内部任务启动秒表并到达第一个等待,然后一路返回DelayExecute
.
与我类似的修复LINQPad上面的例子就是简单地改变这一行:
await Task.Factory.StartNew(async () =>
To this:
await await Task.Factory.StartNew(async () =>