为啥我的循环中的异步委托没有等待

Posted

技术标签:

【中文标题】为啥我的循环中的异步委托没有等待【英文标题】:Why is my async delegate in my loop not awaiting为什么我的循环中的异步委托没有等待 【发布时间】:2018-06-12 11:10:03 【问题描述】:

预期: 我需要定期运行任务。我希望任务运行,然后等待一些延迟,然后再次运行任务......等等。

实际: 我发现 _loadingMethod 被调用/重新输入。我希望转发器中的 while 循环在 await foo() 语句完成之前不会回来。

问题: 有任何想法吗?我觉得我一直在正确地等待调用堆栈。也许我错过了 lambda 或某处的委托中的某些内容..?

    internal static CancellationTokenSource Repeat(Func<Task> foo, TimeSpan interval, int skipFirstFewIntervals = 0)
    
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        TaskWrapper.RunYetThrowOnMainThread( // kick off the loop
            async () =>
            
                await Task.Delay(skipFirstFewIntervals * interval.Milliseconds); // initial delay (if any)
                while (await DelayOrCancel(interval, cancellationTokenSource.Token)) // loop forever or until cancel
                    try
                    
                         await foo();
                    
                    catch (Exception ex)
                    
                                string msg = $"Failed while repeating: foo.Method.Name";
                        throw new Exception(msg, ex);
                    ;
            );
         return cancellationTokenSource;
    

    private static async Task<bool> DelayOrCancel(TimeSpan interval, CancellationToken token)
    
        bool keepGoing;
        try
        
            await Task.Delay(interval, token); // this throws on cancel
            keepGoing = true;
        
        catch (OperationCanceledException)
        
            keepGoing = false; // kill the loop on cancel
        
        return keepGoing;
    

TaskWrapper 类在下面。

   internal static class TaskWrapper
   
        internal static Task RunYetThrowOnMainThread(Action foo, CancellationToken cancellationToken = default(CancellationToken))
            => RunYetThrowOnMainThread(() => Task.Run(foo), cancellationToken);

        internal static Task RunYetThrowOnMainThread(Func<Task> foo, CancellationToken cancellationToken = default(CancellationToken))
        
            return Task.Run(async () =>
            
                try
                
                    await foo();
                
                catch (Exception ex)
                
                    string msg = $"Failed at CPU bound task: foo.Method.Name";
                    ex.ThrowOnMainThread(msg);
                ;
            );
        
   

这是 ThrowOnMainThread ext 方法的 Throw。这样即使我没有观察任务,我也可以强迫前任浮出水面。

    internal static void ThrowOnMainThread(this Exception ex, string msg) => Xamarin.Forms.Device.BeginInvokeOnMainThread(() => throw new Exception(msg, ex));

在调用层它看起来像这样。

    internal CancellationTokenSource Start()
    
        return Repeater.Repeat(LoadAsync, Cfg.LoadingInterval);
    

您可以看到令牌源被向上传递调用堆栈。

在顶层我有类似的东西。

        _incidentLoaderCanceller = _incidentLoader.Start();

其中 _incidentLoaderCanceller 是 CancellationTokenSource。

_incidentLoader 是一个

internal class Loader<TData>

    private readonly Func<TData, Task<bool>> _loadingMethod;
    private readonly IEnumerable<Func<Exception, bool>> _exceptionHandlers;

    public Loader(Func<TData, Task<bool>> loadingMethod, IEnumerable<Func<Exception, bool>> exceptionHandlers)
    
        _loadingMethod = loadingMethod.ThrowIfDefaultT();
        _exceptionHandlers = exceptionHandlers.ThrowIfDefaultT();
    

    internal CancellationTokenSource Start()
    
        return Repeater.Repeat(LoadAsync, Cfg.LoadingInterval);
    

    private async Task LoadAsync()
    
        ... // some looping over this guy LoadAsync(TData data)
    

    ...

    private async Task<bool> LoadAsync(TData data)
    
        bool result = false;
        try
        
            result = await _loadingMethod(data).ConfigureAwait(false);
        
        catch (Exception ex)
        
            ex.HandleOrRethrow(_exceptionHandlers);
        
        return result;
    

【问题讨论】:

您传递给TaskWrapper.RunYetThrowOnMainThread 的委托(隐式)是异步无效的。使方法 RunYetThrowOnMainThread 采用 Func 代替 您的 RunYetThrowOnMainThread 实现存在问题(也有一个隐式异步 void 委托) @DaveM 您希望这会发生什么变化?无论如何,调用者只是忽略了RunYetThrowOnMainThread 返回的Task,因此在该任务完成时改变,或者它的结果是什么,当没有人在看它时并不重要。虽然我个人不喜欢这种设计,但实际上这里调用堆栈的顶部是 async void 设计。 我只是指出我立即看到的完全错误的事情,而没有花时间深入研究这个令人费解的例子。如果有完整的解决方案,我会发布答案。 @dFlat 获得真正帮助的唯一方法是将其减少到 MCVE 【参考方案1】:

我发现并解决了这个问题。原来我的错误处理程序错误地将特定异常标记为不应该处理的异常。上面的代码对我有用。很难让它开发和测试。希望它会帮助别人。

【讨论】:

以上是关于为啥我的循环中的异步委托没有等待的主要内容,如果未能解决你的问题,请参考以下文章

异步循环没有等待

为啥异步等待操作仍然很耗时

Node.js/Mongoose 等待异步 For 循环

此异步等待不应该工作。为啥它会起作用?

ForEach 循环中的异步/等待节点 Postgres 查询

Dart - 在 for 循环中等待所有异步任务