简体   繁体   English

一个动作中的C#异步

[英]C# async within an action

I would like to write a method which accept several parameters, including an action and a retry amount and invoke it. 我想写一个接受几个参数的方法,包括一个动作和一个重试量并调用它。

So I have this code: 所以我有这个代码:

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 is an extention method for Action, its look like this: 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;
    }

Now, the issue: The thread is just disappearing or collapsing without waiting for the action to finish. 现在,问题是:线程正在消失或崩溃而不等待操作完成。

I wrote this example code: 我写了这个示例代码:

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

The fetchSmthFromDb is a simple Task> which fetches something from the db and works perfectly fine when invoked outside of this example. fetchSmthFromDb是一个简单的Task>,它从db中获取一些东西,并且在这个例子之外调用时工作得很好。

Whenever the List<string> test = await fetchSmthFromDb(); 只要List<string> test = await fetchSmthFromDb(); row is invoked, the thread seems to be closing and the Console.WriteLine("#" + num + " " + test[0]); 调用行,线程似乎正在关闭和Console.WriteLine("#" + num + " " + test[0]); not even being triggered, also when debugging the breakpoint never hit. 甚至没有被触发,也是在调试断点时从未命中。

The Final Working Code 最终工作守则

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

The problem is in this line: 问题出在这一行:

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

You start an async operation with the async lambda, but don't return a task to await on. 您使用异步lambda启动异步操作,但不要返回任务以等待。 Ie it runs on worker threads, but you'll never find out when it's done. 即它在工作线程上运行,但你永远不会知道它何时完成。 And your program terminates before the async operation is complete -that's why you don't see any output. 并且您的程序在异步操作完成之前终止 - 这就是您没有看到任何输出的原因。

It needs to be: 它需要是:

return new Func<Task>(async () => ...

UPDATE UPDATE

First, you need to split responsibilities of methods, so you don't mix retry policy (which should not be hardcoded to a check of a boolean result) with running tasks in parallel. 首先,您需要分割方法的职责,因此不要将重试策略(不应该硬编码为检查布尔结果)与并行运行的任务混合使用。

Then, as previously mentioned, you run your while (true) loop 100 times instead of doing things in parallel. 然后,如前所述,您运行while (true)循环100次而不是并行执行。

As @MachineLearning pointed out, use Task.Delay instead of Thread.Sleep . 正如@MachineLearning所指出的,使用Task.Delay而不是Thread.Sleep

Overall, your solution looks like this: 总的来说,您的解决方案如下所示:

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

You need to use the AsyncEnumerator NuGet Package in order to use the ParallelForEachAsync extension method from the System.Collections.Async namespace. 您需要使用AsyncEnumerator NuGet包才能使用System.Collections.Async命名空间中的ParallelForEachAsync扩展方法。

Besides the final complete reengineering, I think it's very important to underline what was really wrong with the original code. 除了最后的完整再造之外,我认为强调原始代码的真正错误是非常重要的。

0) First of all, as @Serge Semenov immediately pointed out, Action has to be replaced with 0)首先,正如@Serge Semenov立即指出的那样,Action必须被替换为

Func<Task>

But there are still other two essential changes. 但仍有其他两个重要变化。

1) With an async delegate as argument it is necessary to use the more recent Task.Run instead of the older pattern new TaskFactory.StartNew (or otherwise you have to add Unwrap() explicitly) 1)使用异步委托作为参数,必须使用更新的Task.Run而不是旧的模式new TaskFactory.StartNew(否则你必须显式添加Unwrap())

2) Moreover the ex() method can't be async since Task.WhenAll must be waited with Wait() and without await. 2)此外ex()方法不能是异步的,因为必须使用Wait()并且没有await等待Task.WhenAll。

At that point, even though there are logical errors that need reengineering, from a pure technical standpoint it does work and the output is produced. 那时,即使存在需要重新设计的逻辑错误,从纯粹的技术角度来看,它确实有效并且产生了输出。

A test is available online: http://rextester.com/HMMI93124 可在线获得测试: http//rextester.com/HMMI93124

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM