[英]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.