簡體   English   中英

一個動作中的C#異步

[英]C# async within an 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是一個簡單的Task>,它從db中獲取一些東西,並且在這個例子之外調用時工作得很好。

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

問題出在這一行:

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

您使用異步lambda啟動異步操作,但不要返回任務以等待。 即它在工作線程上運行,但你永遠不會知道它何時完成。 並且您的程序在異步操作完成之前終止 - 這就是您沒有看到任何輸出的原因。

它需要是:

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

UPDATE

首先,您需要分割方法的職責,因此不要將重試策略(不應該硬編碼為檢查布爾結果)與並行運行的任務混合使用。

然后,如前所述,您運行while (true)循環100次而不是並行執行。

正如@MachineLearning所指出的,使用Task.Delay而不是Thread.Sleep

總的來說,您的解決方案如下所示:

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

您需要使用AsyncEnumerator NuGet包才能使用System.Collections.Async命名空間中的ParallelForEachAsync擴展方法。

除了最后的完整再造之外,我認為強調原始代碼的真正錯誤是非常重要的。

0)首先,正如@Serge Semenov立即指出的那樣,Action必須被替換為

Func<Task>

但仍有其他兩個重要變化。

1)使用異步委托作為參數,必須使用更新的Task.Run而不是舊的模式new TaskFactory.StartNew(否則你必須顯式添加Unwrap())

2)此外ex()方法不能是異步的,因為必須使用Wait()並且沒有await等待Task.WhenAll。

那時,即使存在需要重新設計的邏輯錯誤,從純粹的技術角度來看,它確實有效並且產生了輸出。

可在線獲得測試: http//rextester.com/HMMI93124

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM