【问题标题】:await a Task and silently stop on cancel or failure等待任务并在取消或失败时静默停止
【发布时间】:2015-12-01 14:39:39
【问题描述】:

我想写的是:

async void Foo()
{
    var result = await GetMyTask().IgnoreCancelAndFailure();
    ProcessResult(result);
}

代替:

void Foo()
{
    GetMyTask().ContinueWith(task => ProcessResult(task.Result),
        TaskContinuationOptions.OnlyOnRanToCompletion);
}

但是我不知道如何实现 IgnoreCancelAndFailure 方法,它的签名如下:

//On cancel or failure this task should simply stop and never complete.
Task<T> IgnoreCancelAndFailure<T>(this Task<T> task) 
{
     throw new NotImplementedException(); 
}

如果可能,我应该如何实现 IgnoreCancelAndFailure?

【问题讨论】:

  • 任务取消或失败应该返回什么结果?
  • 它根本不应该返回。此任务的继续也应该获得 Cancelled/Failure 状态。
  • 但是调用者期望返回值......你不能只说“不返回任何东西”
  • @KeyboardDrummer 我明白了你想在这里做什么,但从技术上讲这是不可能的。如果出现故障,您希望该函数停止运行,但是您不能调用一个函数取消另一个函数的运行,您必须将该逻辑构建到调用函数中。
  • @KeyboardDrummer 那么你不需要做任何事情。您正在询问如何创建与另一个任务相同的任务(如果成功则具有相同的结果,如果出错则具有相同的错误,同时产生结果)。只需使用原始的Task

标签: c# .net task


【解决方案1】:

你可以做类似的事情,但你需要知道在失败的情况下你希望方法返回什么,因为返回值是预期的:

public static async Task<T> IgnoreCancelAndFailure<T>(this Task<T> task) 
{
     try
     {
         return await task;
     }
     catch
     {
         return ???; // whatever you want to return in this case
     }
}

如果是 Task 没有结果,只需将 catch 留空(或者可能记录异常...吞下的异常会导致硬调试)

如果你只想在GetMyTask成功时执行ProcessResult,你可以这样做:

async void Foo()
{
    try
    {
        var result = await GetMyTask();
        ProcessResult(result);
    }
    catch(Exception ex)
    {
        // handle the exception somehow, or ignore it (not recommended)
    }
}

【讨论】:

  • 就我个人而言,我强烈建议不要返回null(或“无”),当您尝试附加链接(例如@)时,您可能会引入讨厌的NullReferenceException 987654329@.
  • @KeyboardDrummer,你问的是不可能的。具有非 void 返回类型的方法必须返回值或抛出异常。
  • @ThomasLevesque 是的,捕获异常会起作用。您能否对我自己发布的答案发表意见?它解决了我最初发布的问题,但恐怕它只是“糟糕的风格”。
【解决方案2】:

在终止线程或进程时,您将永远无法阻止您的代码继续期望。请记住,等待任务可以被认为是一个函数调用,它总是必须返回一个值或抛出一个异常。 缩短代码的最接近的方法是创建一个使用 ProcessResult 方法作为 Action 参数的包装函数。 类似的东西:

public static async Task IgnoreCancelAndFailure<T>(this Task<T> task, Action<T> resultProcessor) 
{
    task.ContinueWith(t => resultProcessor(t.Result),
        TaskContinuationOptions.OnlyOnRanToCompletion);
}

async void Foo()
{
    GetMyTask().IgnoreCancelAndFailure(ProcessResult);
}

【讨论】:

    【解决方案3】:

    我想我找到了答案。以下似乎可以解决问题。它使用等待模式。你们能确认这不是邪恶的吗?

    class User
    {
        async void Foo()
        {
            var result = await GetMyTask().IgnoreCancelAndFailure();
            ProcessResult(result);
        }
    }
    
    public static class TaskExtenstions
    {
        public static SilentTask<T> IgnoreCancelAndFailure<T>(this Task<T> task)
        {
            return new SilentTask<T>(task);
        }
    }
    
    public class SilentTask<T>
    {
        private readonly Task<T> _inner;
    
        public SilentTask(Task<T> inner)
        {
            _inner = inner;
        }
    
        public SilentAwaiter GetAwaiter()
        {
            return new SilentAwaiter(_inner);
        }
    
        public class SilentAwaiter : INotifyCompletion
        {
            private readonly TaskAwaiter<T> _inner;
            private readonly Task<T> _task;
    
            public SilentAwaiter(Task<T> task)
            {
                _task = task;
                _inner = task.GetAwaiter();
            }
    
            public bool IsCompleted
            {
                get
                {
                    return _task.Status == TaskStatus.RanToCompletion;
                }
            }
    
            public void OnCompleted(Action continuation)
            {
                _inner.OnCompleted(() =>
                {
                    if (IsCompleted)
                    {
                        continuation();
                    }
                });
            }
    
            public T GetResult()
            {
                return _inner.GetResult();
            }
        }
    }
    

    【讨论】:

    • 当您尝试获取未运行完成的任务的等待者的结果时,这将崩溃,这正是问题试图避免的......
    • 好吧,但是 C# 处理等待模式的方式是它不会调用 GetResult,因为从不调用延续,所以在示例中它不会崩溃。
    • 如果你从未完成它,那么你只是导致程序在出现错误时永远不会继续执行。
    • 是的,这就是我想要的!它与以下代码的行为相同:GetMyTask().ContinueWith(task => ProcessResult(task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
    • 这对我来说似乎太复杂了,尤其是当你可以通过简单地捕获异常来实现同样的事情时......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-20
    • 2017-01-22
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多