【问题标题】:Task scheduling when calling external method调用外部方法时的任务调度
【发布时间】:2015-01-04 05:25:36
【问题描述】:

我有以下扩展,它允许我强制任务超时。

public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeSpan, string message = null)
{
    if (task == await Task.WhenAny(task, Task.Delay(timeSpan)))
        return await task;
    else
    {
        throw new TimeoutException(message);
    }
}

我有以下任务:

private async Task<string> TestTask(PageData page)
{
    await Task.Delay(TimeSpan.FromSeconds(10));
    return "teststring";
}

当我使用以下命令调用任务时,我得到了预期的结果。

var teststring = await TestTask()
                .TimeoutAfter(TimeSpan.FromSeconds(5), "timeout message");

我遇到的问题是,当我调用外部库(在本例中为 c++ CLR 类库)中的方法时,永远不会到达 TimeoutAfter() 中的代码,并且任务永远不会超时。我可以通过使用 Task.Factory.StartNew()(如下)来解决这个问题,将回调传递给长时间运行的任务并在主任务开始执行之前创建我的 Task.Delay 任务,但这不能作为扩展.

private async Task<T> Timeout<T>(Func<Task<T>> taskFunc)
{
    var taskWait = Task.Delay(TimeSpan.FromSeconds(15));
    var task = Task.Factory.StartNew(() => taskFunc());

    if (task == await Task.WhenAny(task, taskWait))
        return await (await task);
    else
    {
        throw new TimeoutException("timeout");
    }
}

为什么 TimeoutAfter() 中的代码似乎只在某些内容位于它正在扩展的任务中时才执行?我可以向扩展程序添加什么功能来防止这种情况发生?

【问题讨论】:

  • 您是否有机会在 Windows 窗体或 WPF UI 中执行此操作?你能制作一个简短但完整的程序来演示这个问题吗?我们对您调用的方法一无所知,这无济于事 - 它是异步的吗?#
  • 您应该知道,这并不是传统意义上的“超时”。您的任务不会中止,只是您的等待超时.. 任务将继续进行,如果它是无限循环或其他长时间运行的任务,它可能会产生非常奇怪的副作用。您可能也不知道超时后是否会在该任务中引发异常。
  • @JonSkeet 正在执行的代码在控制台应用程序中,任务中调用的方法是调用 c++ CLR 类库中的方法,该类库又调用与映像库关联的外部 dll .是否存在某些条件会同步执行任务并且不会调用扩展?
  • 我相信@JonSkeet 是在正确的轨道上。检查您的失败代码是如何被调用的。如果WaitResult 有任何用途,您可能会遇到我在博客中描述的classic deadlock issue。作为一个简单的测试,将.ConfigureAwait(false) 添加到TimeoutAfter 中的每个await 表达式,看看是否有效。
  • @jimmyjambles:请发布一个最小的、可重现的示例。

标签: c# async-await


【解决方案1】:

我认为你的主要问题是当你尝试使用扩展方法时你甚至没有开始你的任务。

Task 是完全异步的,must be started 可以访问其代码。开始任务的一种方法是使用 StartNew,就像您在工厂中尝试的那样。

但您也可以只调用Start 方法。唯一需要注意的是,如果 start 已经开始或已经完成,调用 Start 时会出现异常。

所以,我认为您的解决方案可以是:

public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeSpan, string message = null)
{
    if(task.Status != TaskStatus.Running && task.Status != TaskStatus.RanToCompletion){
       task.Start();
    }
    if (task == await Task.WhenAny(task, Task.Delay(timeSpan)))
       return await task;
    else
    {
        throw new TimeoutException(message);
    }
}

【讨论】:

  • 您永远不需要使用Start 显式启动async 任务
  • 一旦你持有对它已经启动的任务的引用,我遇到的问题是扩展中没有代码被某些基本任务调用
猜你喜欢
  • 2011-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-06
  • 1970-01-01
  • 2018-10-05
  • 2012-02-04
相关资源
最近更新 更多