一个动作中的C#异步

时间:2016-08-28 13:31:23

标签: c# task action

我想编写一个接受多个参数的方法,包括一个动作和一个重试量,并调用它。

所以我有这段代码:

public static IEnumerable<Task> RunWithRetries<T>(List<T> source, int threads, Func<T, Task<bool>> action, int retries, string method)
    {
        object lockObj = new object();
        int index = 0;

        return new Action(async () =>
        {
            while (true)
            {
                T item;
                lock (lockObj)
                {
                    if (index < source.Count)
                    {
                        item = source[index];
                        index++;
                    }
                    else
                        break;
                }

                int retry = retries;
                while (retry > 0)
                {
                    try
                    {
                        bool res = await action(item);
                        if (res)
                            retry = -1;
                        else
                            //sleep if not success..
                            Thread.Sleep(200);

                    }
                    catch (Exception e)
                    {
                        LoggerAgent.LogException(e, method);
                    }
                    finally
                    {
                        retry--;
                    }
                }
            }
        }).RunParallel(threads);
    }

RunParallel是Action的扩展方法,它看起来像这样:

public static IEnumerable<Task> RunParallel(this Action action, int amount)
    {
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < amount; i++)
        {
            Task task = Task.Factory.StartNew(action);
            tasks.Add(task);
        }
        return tasks;
    }

现在,问题是:线程正在消失或崩溃而不等待操作完成。

我写了这个示例代码:

private static async Task ex()
    {
        List<int> ints = new List<int>();
        for (int i = 0; i < 1000; i++)
        {
            ints.Add(i);
        }

        var tasks = RetryComponent.RunWithRetries(ints, 100, async (num) =>
        {
            try
            {
                List<string> test = await fetchSmthFromDb();
                Console.WriteLine("#" + num + "  " + test[0]);
                return test[0] == "test";
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
                return false;
            }

        }, 5, "test");

        await Task.WhenAll(tasks);
    }

fetchSmthFromDb是一个简单的任务&gt;从数据库中获取内容并在此示例之外调用时工作完全正常。

每当调用List<string> test = await fetchSmthFromDb();行时,线程似乎正在关闭,Console.WriteLine("#" + num + " " + test[0]);甚至没有被触发,同时调试断点永远不会被触发。

最终工作代码

private static async Task DoWithRetries(Func<Task> action, int retryCount, string method)
    {
        while (true)
        {
            try
            {
                await action();
                break;
            }
            catch (Exception e)
            {
                LoggerAgent.LogException(e, method);
            }

            if (retryCount <= 0)
                break;

            retryCount--;
            await Task.Delay(200);
        };
    }

    public static async Task RunWithRetries<T>(List<T> source, int threads, Func<T, Task<bool>> action, int retries, string method)
    {
        Func<T, Task> newAction = async (item) =>
        {
            await DoWithRetries(async ()=>
            {
                await action(item);
            }, retries, method);
        };
        await source.ParallelForEachAsync(newAction, threads);
    }

2 个答案:

答案 0 :(得分:6)

问题出在这一行:

TopMainFragment

您使用异步lambda启动异步操作,但不要返回任务以等待。即它运行在工作线程上,但您永远不会知道它何时完成。并且您的程序在异步操作完成之前终止 - 这就是您没有看到任何输出的原因。

需要:

return new Action(async () => ...

<强>更新

首先,您需要分配方法的职责,因此不要将重试策略(不应该硬编码到布尔结果的检查)与并行运行的任务混合。

然后,如前所述,您运行return new Func<Task>(async () => ... 循环100次而不是并行处理。

正如@MachineLearning指出的那样,使用while (true)代替Task.Delay

总的来说,您的解决方案如下所示:

Thread.Sleep

您需要使用AsyncEnumerator NuGet Package才能使用using System.Collections.Async; static async Task DoWithRetries(Func<Task> action, int retryCount, string method) { while (true) { try { await action(); break; } catch (Exception e) { LoggerAgent.LogException(e, method); } if (retryCount <= 0) break; retryCount--; await Task.Delay(millisecondsDelay: 200); }; } static async Task Example() { List<int> ints = new List<int>(); for (int i = 0; i < 1000; i++) ints.Add(i); Func<int, Task> actionOnItem = async item => { await DoWithRetries(async () => { List<string> test = await fetchSmthFromDb(); Console.WriteLine("#" + item + " " + test[0]); if (test[0] != "test") throw new InvalidOperationException("unexpected result"); // will be re-tried }, retryCount: 5, method: "test"); }; await ints.ParallelForEachAsync(actionOnItem, maxDegreeOfParalellism: 100); } 命名空间中的ParallelForEachAsync扩展名方法。

答案 1 :(得分:0)

除了最后的完整再造之外,我认为强调原始代码的真正错误非常重要。

0)首先,正如@Serge Semenov立即指出的那样,Action必须被替换为

Group_Y

但是还有其他两个必要的变化。

1)使用异步委托作为参数,必须使用更新的Task.Run而不是旧模式new TaskFactory.StartNew(否则你必须显式添加Unwrap())

2)此外ex()方法不能异步,因为Task.WhenAll必须等待Wait()并且没有等待。

此时,即使存在需要重新设计的逻辑错误,从纯粹的技术角度来看,它确实有效并且产生了输出。

可以在线获得测试:http://rextester.com/HMMI93124