一种等待任务时忽略特定类型异常的简单方法

问题描述 投票:2回答:4

[等待Task时,我想有一种简单的方法来忽略特定类型的异常,例如TaskOperationCanceledException或其他。我想到了编写一个扩展方法的方法,该方法可以包装我的TimeoutException,并抑制我会给它作为参数的Task类型。所以我写了这个:

Exception

我可以这样使用它,并且可以正常使用:

public static async Task Ignore<TException>(this Task task)
    where TException : Exception
{
    try
    {
        await task;
    }
    catch (Exception ex)
    {
        if (ex is TException) return;
        throw;
    }
}

问题是它仅支持一种类型的异常,我必须为两种类型编写另一种版本,另一种针对三种类型,另一种针对四种,依此类推。这已经失控了,因为我还想要此扩展程序的重载,它将忽略返回结果(await myTask.Ignore<OperationCanceledException>(); )的任务的异常。因此,我还需要另一套扩展方法来解决这种情况。

这是我的实现,用于在等待Task<Result>时忽略一种异常类型:

Task<Result>

用法:

public static async Task<TResult> Ignore<TResult, TException>(
    this Task<TResult> task, TResult defaultValue)
    where TException : Exception
{
    try
    {
        return await task;
    }
    catch (Exception ex)
    {
        if (ex is TException) return defaultValue;
        throw;
    }
}

我的问题是,我可以以能够处理多种类型的被忽略异常的方式编写这些方法,而不必为每种类型的数量编写单独的方法吗?

c# exception async-await task-parallel-library
4个回答
1
投票

是,可以,但是不能使用var result = await myInt32Task.Ignore<int, OperationCanceledException>(0);

如果您愿意将Generics传递为Type,则可以这样做:

params

现在,这没有吸引力了[[和您没有public static async Task<TResult> Ignore<TResult> (this Task<TResult> task, TResult defaultValue, params Type[] typesToIgnore) { try { return await task; } catch (Exception ex) { if (typesToIgnore.Any(type => type.IsAssignableFrom(ex.GetType()))) { return defaultValue; } throw; } } generic constraint ...),但应该可以完成工作。


1
投票
我将依靠任务链来避免在扩展方法中初始化任务执行。

where TException


1
投票
据我了解,您的期望是在等待您的public static Task<TResult> Ignore<TResult>(this Task<TResult> self, TResult defaultValue, params Type[] typesToIgnore) { return self.ContinueWith( task => { if (task.IsCanceled && (typesToIgnore.Any(t => typeof(OperationCanceledException) == t || t.IsSubclassOf(typeof(OperationCanceledException))))) { return defaultValue; } if (!task.IsFaulted) { return task.Result; } if (typesToIgnore.Any(t => task.Exception.InnerException.GetType() == t || task.Exception.InnerException.GetType().IsSubclassOf(t))) { return defaultValue; } throw task.Exception.InnerException; }, TaskContinuationOptions.ExecuteSynchronously); } 时能够忽略不止一种类型的异常。您自己的解决方案似乎是我的最佳选择。您始终可以使用建议的解决方案简单地“链接”呼叫:

Task

这应该返回一个Task,它实质上是三个嵌套的try-catch块。除非您

active想要更优雅的definition,否则总可以摆脱更优雅的usage;)

唯一不太好的问题是,在await myTask.Ignore<OperationCanceledException>().Ignore<IOException>().Ignore<TimeoutException>(); 返回TResult的情况下,您必须多次“传播”默认值。如果这不是一个很大的问题,那么您可以摆脱相同的问题:

Task

作为“晦涩的”奖励,请注意,通过这种方式,您可以非常容易地为

不同的异常提供不同的默认返回值。因此,必须重复默认值毕竟可以为您带来好处!例如,您可能有理由在TimeOutException上返回0,而在OperationCanceledException上返回-1,依此类推。如果最后这成为您的目的,请记住使用await myTask.Ignore<int, OperationCanceledException>(0).Ignore<int, TimeoutException>(0); 可能不是您真正想要的,而不是确切的is相等,因为您可能还希望针对源自同一Type的不同异常返回不同的默认值(这种分析开始变得相当复杂,但是您当然知道了)。

更新

基于Type的版本的链式调用“优雅”的最终水平似乎必须以编译时类型检查为代价:

TResult

如果您准备牺牲编译时的安全性,则可以通过仅声明您希望忽略的异常并最后添加“ casting”调用来减轻重复的痛苦。当然,这有其自身的问题,但是仅在需要忽略多个异常时才需要这样做。否则,您可以使用一个单一的类型并相应地调用public static async Task<TResult> Ignore<TResult, TException>(
this Task<TResult> task, TResult defaultValue)
    where TException : Exception
{
    try
    {
        return await task;
    }
    catch (Exception ex)
    {
        if (ex is TException) return defaultValue;
        throw;
    }
}

public static async Task<TResult> Ignore<TResult, TException>(
this Task task, TResult defaultValue)
    where TException : Exception
{
    try
    {
        return await (Task<TResult>)task;
    }
    catch (Exception ex)
    {
        if (ex is TException) return defaultValue;
        throw;
    }
}

public static Task Ignore<TException>(this Task task)
    where TException : Exception
{
    try
    {
        //await seems to create a new Task that is NOT the original task variable.
        //Therefore, trying to cast it later will fail because this is not a Task<TResult>
        //anymore (the Task<TResult> has been "swallowed").

        //For that reason, await the task in an independent function.
        Func<Task> awaitableCallback = async () => await task;

        awaitableCallback();

        //And return the original Task, so that it can be cast back correctly if necessary.
        return task;
    }
    catch (Exception ex)
    {
        //Same upon failure, return the original task.
        if (ex is TException) return task;
        throw;
    }
}

public static async Task<int> TestUse()
{
    Task<int> t = Task<int>.Run(() => 111);

    int result = await t.Ignore<TaskCanceledException>()
                        .Ignore<InvalidOperationException>()
                        .Ignore<int, TimeoutException>(0);

    return result;
}

编辑

基于Ignore<TResult, TException>(),因为异步/等待模式似乎产生了一个新的Task,该任务包装了在上面的Ignore方法中传递的awaitable task参数,因此示例调用实际上以relevant comment失败,因为中间的Ignore调用事实

已更改任务和原始任务在呼叫链中的某个位置丢失了。因此,已对“ casting” InvalidCastException方法进行了稍许调整,以使最后可以返回原始任务,从而可以在所有Ignore调用之后,最后一次基于Ignore的[ C0]通话。上面的代码已被修改以纠正这种情况。这不会使整个模式特别优雅,但至少现在似乎可以正常使用。


1
投票
[Vector Sigma TResult中指出,可以链接我原来的一种类型的方法来实现忽略多种类型的异常。但是,由于需要重复Ignore类型和answer,因此将IgnoreTask<TResult>链接起来很麻烦。阅读TResult的可接受答案后,我想出了解决方法。我需要介绍一个通用的任务包装器defaultValue,它将保留此状态并包含可链接的方法a question about partial type inference。这是预期的用法:

struct

这里是我命名为Ignore的任务包装程序,以及扩展方法var result = await myInt32Task.WithDefaultValue(0)
    .Ignore<OperationCanceledException>()
    .Ignore<TimeoutException>();

TaskWithDefaultValue

© www.soinside.com 2019 - 2024. All rights reserved.