【发布时间】:2020-04-30 10:08:44
【问题描述】:
我已经阅读了一些关于 foreach 循环中的多线程的 stackoverflow 线程,但我不确定我是否理解并正确使用它。
我已经尝试了多种方案,但我没有看到性能有太大的提高。
这是我认为运行异步任务,但使用单线程在循环中同步运行:
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
foreach (IExchangeAPI selectedApi in selectedApis)
{
if (exchangeSymbols.TryGetValue(selectedApi.Name, out symbol))
{
ticker = await selectedApi.GetTickerAsync(symbol);
}
}
stopWatch.Stop();
这是我希望异步运行(仍然使用单线程) - 我已经预料到速度会有所提高:
List<Task<ExchangeTicker>> exchTkrs = new List<Task<ExchangeTicker>>();
stopWatch.Start();
foreach (IExchangeAPI selectedApi in selectedApis)
{
if (exchangeSymbols.TryGetValue(selectedApi.Name, out symbol))
{
exchTkrs.Add(selectedApi.GetTickerAsync(symbol));
}
}
ExchangeTicker[] retTickers = await Task.WhenAll(exchTkrs);
stopWatch.Stop();
这是我希望在多线程中异步运行:
stopWatch.Start();
Parallel.ForEach(selectedApis, async (IExchangeAPI selectedApi) =>
{
if (exchangeSymbols.TryGetValue(selectedApi.Name, out symbol))
{
ticker = await selectedApi.GetTickerAsync(symbol);
}
});
stopWatch.Stop();
秒表结果解释如下:
Console.WriteLine("Time elapsed (ns): {0}", stopWatch.Elapsed.TotalMilliseconds * 1000000);
控制台输出:
Time elapsed (ns): 4183308100
Time elapsed (ns): 4183946299.9999995
Time elapsed (ns): 4188032599.9999995
现在,速度提升看起来微不足道。我做错了什么还是或多或少是我应该期待的?我想写入文件会更好地检查。
您是否介意确认我正确解释了不同的用例?
最后,使用 foreach 循环从多个平台并行获取代码可能不是最好的方法。欢迎就如何改进这一点提出建议。
编辑
请注意,我使用的是 ExchangeSharp 代码库,您可以找到 here
GerTickerAsync() 方法如下所示:
public virtual async Task<ExchangeTicker> GetTickerAsync(string marketSymbol)
{
marketSymbol = NormalizeMarketSymbol(marketSymbol);
return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetTickerAsync(marketSymbol), nameof(GetTickerAsync), nameof(marketSymbol), marketSymbol);
}
对于 Kraken API,您有:
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
{
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", NormalizeMarketSymbol(marketSymbol) } });
JToken ticker = apiTickers[marketSymbol];
return await ConvertToExchangeTickerAsync(marketSymbol, ticker);
}
以及缓存方法:
public static async Task<T> CacheMethod<T>(this ICache cache, Dictionary<string, TimeSpan> methodCachePolicy, Func<Task<T>> method, params object?[] arguments) where T : class
{
await new SynchronizationContextRemover();
methodCachePolicy.ThrowIfNull(nameof(methodCachePolicy));
if (arguments.Length % 2 == 0)
{
throw new ArgumentException("Must pass function name and then name and value of each argument");
}
string methodName = (arguments[0] ?? string.Empty).ToStringInvariant();
string cacheKey = methodName;
for (int i = 1; i < arguments.Length;)
{
cacheKey += "|" + (arguments[i++] ?? string.Empty).ToStringInvariant() + "=" + (arguments[i++] ?? string.Empty).ToStringInvariant("(null)");
}
if (methodCachePolicy.TryGetValue(methodName, out TimeSpan cacheTime))
{
return (await cache.Get<T>(cacheKey, async () =>
{
T innerResult = await method();
return new CachedItem<T>(innerResult, CryptoUtility.UtcNow.Add(cacheTime));
})).Value;
}
else
{
return await method();
}
}
【问题讨论】:
-
这是一个很常见的误解,但 C# 中的异步方法调用并不是并行处理的神奇门票。在上面的所有示例中,one and only one thread is ever being used:您从中调用方法的主线程。
-
@PatrickTucci,你是说“Parallel.ForEach”不会并行运行我的任务吗?
-
对不起,我没有看到那个例子。那是我的错误。
Parallel.ForEach和其他 PLINQ 调用可以自动在多个线程池线程上运行查询。传统的异步方法调用不会自动创建并行性,大多数新接触 TPL 和async/await关键字的程序员都相信他们会这样做。这就是我最初发表评论的原因。但你是对的,Parallel.ForEach确实使用了多个线程。 -
@PatrickTucci,很好,所以我的 3 种方法和对它们的解释没有错吗?
-
您的解释似乎对所有三种方法都是正确的。我假设,就像 TomTom 提到的那样,您没有看到执行时间减少,因为 API 限制了您的调用。
标签: c# multithreading async-await task