异步Task <T>的超时以及其他异常处理

时间:2019-11-03 00:45:30

标签: c# asynchronous timeout interop

在我的项目中,我从动态链接库引用类型和接口。 使用此特定库时,我要做的第一件事是创建EA.Repository的实例,该实例在该库中定义,并用作进一步使用的入口点。

实例化EA.Repository repository = new EA.Repository()在后​​台执行一些复杂的操作,我发现自己遇到了三种可能的结果:

  1. 实例化需要一些时间,但最终成功完成
  2. (立即或一段时间后)引发异常
  3. 实例化永远阻止(在这种情况下,我想取消并通知用户)

我能够使用Task提出异步方法:

public static void Connect()
{
    // Do the lengthy instantiation asynchronously
    Task<EA.Repository> task = Task.Run(() => { return new EA.Repository(); });

    bool isCompletedInTime;

    try
    {
        // Timeout after 5.0 seconds
        isCompletedInTime = task.Wait(5000);
    }
    catch (Exception)
    {
        // If the instantiation fails (in time), throw a custom exception
        throw new ConnectionException();
    }

    if (isCompletedInTime)
    {
        // If the instantiation finishes in time, store the object for later
        EapManager.Repository = task.Result;
    }
    else
    {       
        // If the instantiation did not finish in time, throw a custom exception
        throw new TimeoutException();
    }
}

(我知道,您可能已经在这里发现了很多问题。请耐心等待,建议将不胜感激!)

到目前为止,这种方法仍然有效-我可以模拟“异常”和“超时”场景,并获得所需的行为。

但是,我发现了另一种极端情况:假设实例化任务花费的时间足够长,以至于超时到期然后引发异常。在这种情况下,有时我会以AggregateException结尾,说尚未遵守任务。

我正在努力寻找可行的解决方案。超时到期后,我无法真正取消任务,因为阻塞实例化显然使我无法使用CancellationToken方法。

我唯一能想到的就是在抛出我的自定义TimeoutException之前立即开始异步观察任务(即开始另一个任务):

Task observerTask = Task.Run(() => {
    try { task.Wait(); }
    catch (Exception) { }
});

throw new TimeoutException();

当然,如果实例化确实永远阻塞了,那么我已经完成了第一个任务,即永远无法完成。有了观察者任务,现在我什至有两个!

我对整个方法不太安全,因此欢迎提出任何建议!

非常感谢您!

2 个答案:

答案 0 :(得分:2)

我不确定我是否完全理解您要实现的目标,但是如果您做这样的事情-

public static void Connect()
{
    Task<EA.Repository> _realWork = Task.Run(() => { return new EA.Repository(); });
    Task _timeoutTask = Task.Delay(5000);
    Task.WaitAny(new Task[]{_realWork, timeoutTask});
    if (_timeoutTask.Completed)
    {
        // timed out
    }
    else
    {
        // all good, access _realWork.Result
    }
}

或者您甚至可以缩短一点-

public static void Connect()
{
    Task<EA.Repository> _realWork = Task.Run(() => { return new EA.Repository(); });
    var completedTaskIndex = Task.WaitAny(new Task[]{_realWork}, 5000);
    if (completedTaskIndex == -1)
    {
        // timed out
    }
    else
    {
        // all good, access _realWork.Result
    }
}

您也可以始终call Task.Run with a CancellationToken超时,但这会引发异常-上述解决方案使您可以控制行为,而不会引发异常(即使您始终可以try/catch

答案 1 :(得分:0)

这里是扩展方法,可用于显式观察未被观察时可能失败的任务:

public static Task<T> AsObserved<T>(this Task<T> task)
{
    task.ContinueWith(t => t.Exception);
    return task;
}

用法示例:

var task = Task.Run(() => new EA.Repository()).AsObserved();