为什么我的循环中的异步委托不等待

时间:2018-01-02 18:45:28

标签: c# asynchronous xamarin async-await

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

实际值: 我发现_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);
                };
            });
        }
   }

这是Throw on ThrowOnMainThread ext方法。通过这种方式,即使我没有观察任务,我也可以强迫前者浮出水面。

    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;
    }
}

1 个答案:

答案 0 :(得分:-1)

我发现并解决了这个问题。事实证明我的错误处理程序错误地将特定异常标记为不应该处理的特定异常。上面的代码适合我。很难对它进行开发和测试。希望它会帮助别人。