HttpClient 的奇怪异步问题
Odd Async Issues with HttpClient
正在使用 HttpClient 将 WebClient 代码从 .Net Framework 4.6.1 转换为 NetStandard 1.6,遇到一个奇怪的问题。这是我有问题的代码块:
public double TestDownloadSpeed(Server server, int simultaniousDownloads = 2, int retryCount = 2)
{
var testData = GenerateDownloadUrls(server, retryCount);
return TestSpeed(testData, async (client, url) =>
{
var data = await client.GetByteArrayAsync(url);
return data.Length;
}, simultaniousDownloads);
}
public double TestUploadSpeed(Server server, int simultaniousUploads = 2, int retryCount = 2)
{
var testData = GenerateUploadData(retryCount);
return TestSpeed(testData, async (client, uploadData) =>
{
client.PostAsync(server.Url, new StringContent(uploadData.ToString())).RunSynchronously();
return uploadData[0].Length;
}, simultaniousUploads);
}
private static double TestSpeed<T>(IEnumerable<T> testData, Func<HttpClient, T, Task<int>> doWork, int concurencyCount = 2)
{
var timer = new Stopwatch();
var throttler = new SemaphoreSlim(concurencyCount);
timer.Start();
var downloadTasks = testData.Select(async data =>
{
await throttler.WaitAsync().ConfigureAwait(true);
var client = new CoreSpeedWebClient();
try
{
var size = await doWork(client, data).ConfigureAwait(true);
return size;
}
finally
{
client.Dispose();
throttler.Release();
}
}).ToArray();
Task.Run(() => downloadTasks);
timer.Stop();
double totalSize = downloadTasks.Sum(task => task.Result);
return (totalSize * 8 / 1024) / ((double)timer.ElapsedMilliseconds / 1000);
}
因此,当调用 TestDownloadSpeed 函数时,一切都按预期工作,但是当我调用 TestUploadSpeed 方法时,我收到错误 InvalidOperationException: RunSynchronously 可能不会在未绑定到委托的任务上调用,例如从异步方法返回的任务。* 在 TestSpeed 的这一部分。
double totalSize = downloadTasks.Sum(task => task.Result);
我真的绞尽脑汁想弄清楚 TestUploadSpeed 中的什么东西导致了问题。有人有任何提示可以指出正确的方向吗?
如果有帮助,这里是 .Net 4.6.1 代码,可以正常工作,所以我的翻译中可能有什么地方不对?另外,原始代码的运行速度大约快 5 倍,所以不确定那是什么...
public double TestDownloadSpeed(Server server, int simultaniousDownloads = 2, int retryCount = 2)
{
var testData = GenerateDownloadUrls(server, retryCount);
return TestSpeed(testData, async (client, url) =>
{
var data = await client.DownloadDataTaskAsync(url).ConfigureAwait(false);
return data.Length;
}, simultaniousDownloads);
}
public double TestUploadSpeed(Server server, int simultaniousUploads = 2, int retryCount = 2)
{
var testData = GenerateUploadData(retryCount);
return TestSpeed(testData, async (client, uploadData) =>
{
await client.UploadValuesTaskAsync(server.Url, uploadData).ConfigureAwait(false);
return uploadData[0].Length;
}, simultaniousUploads);
}
private static double TestSpeed<T>(IEnumerable<T> testData, Func<WebClient, T, Task<int>> doWork, int concurencyCount = 2)
{
var timer = new Stopwatch();
var throttler = new SemaphoreSlim(concurencyCount);
timer.Start();
var downloadTasks = testData.Select(async data =>
{
await throttler.WaitAsync().ConfigureAwait(false);
var client = new SpeedTestWebClient();
try
{
var size = await doWork(client, data).ConfigureAwait(false);
return size;
}
finally
{
client.Dispose();
throttler.Release();
}
}).ToArray();
Task.WaitAll(downloadTasks);
timer.Stop();
double totalSize = downloadTasks.Sum(task => task.Result);
return (totalSize * 8 / 1024) / ((double)timer.ElapsedMilliseconds / 1000);
}
tl;dr
要修复您的具体示例,您需要调用 Wait()
以同步等待 Task
完成。 不是 RunSynchronously()
.
但您可能实际上想要 await
Task
以允许异步完成。 Wait()
在大多数情况下对性能来说不是很好,并且有一些特征如果使用不当可能会导致死锁。
详细解释
RunSynchronously()
与 Wait()
的用法略有不同。它 运行s Task
同步,而 Wait
将 wait 同步但不指示任何内容关于它应该如何 运行.
RunSynchronously()
它在现代 TPL 使用中不是很有用。它只能在 cold Task
-- 尚未启动的情况下调用。
这不是很有用的原因是几乎每个返回 Task
的库方法都会返回 hot Task
-- 一个那已经开始了。这包括来自 HttpClient
的那些。当您在已经启动的 Task
上调用它时,您会得到刚刚 运行 进入的异常。
正在使用 HttpClient 将 WebClient 代码从 .Net Framework 4.6.1 转换为 NetStandard 1.6,遇到一个奇怪的问题。这是我有问题的代码块:
public double TestDownloadSpeed(Server server, int simultaniousDownloads = 2, int retryCount = 2)
{
var testData = GenerateDownloadUrls(server, retryCount);
return TestSpeed(testData, async (client, url) =>
{
var data = await client.GetByteArrayAsync(url);
return data.Length;
}, simultaniousDownloads);
}
public double TestUploadSpeed(Server server, int simultaniousUploads = 2, int retryCount = 2)
{
var testData = GenerateUploadData(retryCount);
return TestSpeed(testData, async (client, uploadData) =>
{
client.PostAsync(server.Url, new StringContent(uploadData.ToString())).RunSynchronously();
return uploadData[0].Length;
}, simultaniousUploads);
}
private static double TestSpeed<T>(IEnumerable<T> testData, Func<HttpClient, T, Task<int>> doWork, int concurencyCount = 2)
{
var timer = new Stopwatch();
var throttler = new SemaphoreSlim(concurencyCount);
timer.Start();
var downloadTasks = testData.Select(async data =>
{
await throttler.WaitAsync().ConfigureAwait(true);
var client = new CoreSpeedWebClient();
try
{
var size = await doWork(client, data).ConfigureAwait(true);
return size;
}
finally
{
client.Dispose();
throttler.Release();
}
}).ToArray();
Task.Run(() => downloadTasks);
timer.Stop();
double totalSize = downloadTasks.Sum(task => task.Result);
return (totalSize * 8 / 1024) / ((double)timer.ElapsedMilliseconds / 1000);
}
因此,当调用 TestDownloadSpeed 函数时,一切都按预期工作,但是当我调用 TestUploadSpeed 方法时,我收到错误 InvalidOperationException: RunSynchronously 可能不会在未绑定到委托的任务上调用,例如从异步方法返回的任务。* 在 TestSpeed 的这一部分。
double totalSize = downloadTasks.Sum(task => task.Result);
我真的绞尽脑汁想弄清楚 TestUploadSpeed 中的什么东西导致了问题。有人有任何提示可以指出正确的方向吗?
如果有帮助,这里是 .Net 4.6.1 代码,可以正常工作,所以我的翻译中可能有什么地方不对?另外,原始代码的运行速度大约快 5 倍,所以不确定那是什么...
public double TestDownloadSpeed(Server server, int simultaniousDownloads = 2, int retryCount = 2)
{
var testData = GenerateDownloadUrls(server, retryCount);
return TestSpeed(testData, async (client, url) =>
{
var data = await client.DownloadDataTaskAsync(url).ConfigureAwait(false);
return data.Length;
}, simultaniousDownloads);
}
public double TestUploadSpeed(Server server, int simultaniousUploads = 2, int retryCount = 2)
{
var testData = GenerateUploadData(retryCount);
return TestSpeed(testData, async (client, uploadData) =>
{
await client.UploadValuesTaskAsync(server.Url, uploadData).ConfigureAwait(false);
return uploadData[0].Length;
}, simultaniousUploads);
}
private static double TestSpeed<T>(IEnumerable<T> testData, Func<WebClient, T, Task<int>> doWork, int concurencyCount = 2)
{
var timer = new Stopwatch();
var throttler = new SemaphoreSlim(concurencyCount);
timer.Start();
var downloadTasks = testData.Select(async data =>
{
await throttler.WaitAsync().ConfigureAwait(false);
var client = new SpeedTestWebClient();
try
{
var size = await doWork(client, data).ConfigureAwait(false);
return size;
}
finally
{
client.Dispose();
throttler.Release();
}
}).ToArray();
Task.WaitAll(downloadTasks);
timer.Stop();
double totalSize = downloadTasks.Sum(task => task.Result);
return (totalSize * 8 / 1024) / ((double)timer.ElapsedMilliseconds / 1000);
}
tl;dr
要修复您的具体示例,您需要调用 Wait()
以同步等待 Task
完成。 不是 RunSynchronously()
.
但您可能实际上想要 await
Task
以允许异步完成。 Wait()
在大多数情况下对性能来说不是很好,并且有一些特征如果使用不当可能会导致死锁。
详细解释
RunSynchronously()
与 Wait()
的用法略有不同。它 运行s Task
同步,而 Wait
将 wait 同步但不指示任何内容关于它应该如何 运行.
RunSynchronously()
它在现代 TPL 使用中不是很有用。它只能在 cold Task
-- 尚未启动的情况下调用。
这不是很有用的原因是几乎每个返回 Task
的库方法都会返回 hot Task
-- 一个那已经开始了。这包括来自 HttpClient
的那些。当您在已经启动的 Task
上调用它时,您会得到刚刚 运行 进入的异常。