简短问题:
为什么.Net Framework添加了很多* Async版本的方法而不是开发人员只使用Task.Run
异步运行同步方法?
详细问题:
Tasks
我不明白的是库中* Async方法的目的。
假设您有两行代码:
F1();
F2();
关于数据/控制流程,只有两种情况:
F2
需要在F1
完成后执行。F2
无需等待F1
完成。我没有看到任何其他情况。我没有看到任何一般需要知道执行某些功能的具体线程(除了UI)。线程中代码的基本执行模式是同步的。并行性需要多个线程。异步性基于并行性和代码重新排序。但基数仍然是同步的。
F1
的工作量很小时,差异无关紧要。但是当A花费大量时间完成时,我们可能需要查看情况,如果F2
不需要等待F1
完成,我们可以运行F1
与F2
平行。
很久以前我们使用线程/线程池来做到这一点。现在我们有Tasks
。
如果我们想并行运行F1
和F2
,我们可以写一下:
var task1 = Task.Run(F1);
F2();
任务很酷,我们可以在最终需要完成任务的地方使用await
。
到目前为止,我认为没有必要制作F1Async()
方法。
现在,我们来看一些特殊情况。
我看到的唯一真正特殊情况是UI。 UI线程是特殊的并且停止它会使UI冻结很糟糕。
在我看来,Microsoft建议我们标记UI事件处理程序async
。标记方法async
意味着我们可以使用await
关键字基本上在另一个线程上安排繁重的处理并释放UI线程,直到处理完成。
我不能再得到的是为什么我们需要任何* Async方法来等待它们。我们总是可以写await Task.Run(F1);
。我们为什么需要F1Async
?
你可能会说* Async方法使用一些特殊的魔法(比如处理外部信号),使它们比同步方法更有效。问题是,我不认为这是事实。
让我们看一下Stream.ReadAsync
。如果查看源代码,ReadAsync
只会浪费数百行铃声和口哨代码来创建一个只调用同步Read
方法的任务。那为什么我们需要呢?为什么不将Task.Run
与Stream.Read
一起使用?
这就是为什么我不理解通过创建同步方法的简单*异步副本来膨胀库的原因。 MS甚至可以添加语法糖,这样我们就可以写await async Stream.Read
而不是await Stream.ReadAsync
或Task.Run(Stream.Read)
。
现在您可能会问“为什么不将* Async方法作为唯一方法并删除同步方法?”。正如我之前所说,基本代码执行模式是同步的。异步运行同步方法很容易,但不是另一种方式。
那么,.Net Framework中* Async方法的目的是什么,因为能够使用Task.Run异步运行任何方法?
P.S。如果非冻结UI非常重要,为什么不默认运行处理程序异步并防止任何冻结的可能性?
“无线程”参数:
回答这个问题的人似乎暗示* Async方法的优势在于它们非常有效,因为它们不会创建新线程。问题是我没有看到这样的行为。并行异步任务的行为与我想的一样 - 为每个并行任务创建(或从线程池中获取)线程(并非所有任务都是并行执行)。
这是我的测试代码:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication32167 {
class Program {
static async Task TestAsync() {
var httpClient = new HttpClient() { Timeout = TimeSpan.FromMinutes(20) };
var tasks = Enumerable.Range(1, 100).Select((i) =>
httpClient.GetStringAsync("http://localhost/SlowWebsite/"));
Console.WriteLine("Threads before completion: " + Process.GetCurrentProcess().Threads.Count);
await Task.WhenAll(tasks);
Console.WriteLine("Threads after completion: " + Process.GetCurrentProcess().Threads.Count);
}
static void Main(string[] args) {
Console.WriteLine("Threads at start: " + Process.GetCurrentProcess().Threads.Count);
var timer = new Stopwatch();
timer.Start();
var testTask = TestAsync();
var distinctThreadIds = new HashSet<int>();
while (!testTask.IsCompleted) {
var threadIds = Process.GetCurrentProcess().Threads.OfType<ProcessThread>().Select(thread => thread.Id).ToList();
distinctThreadIds.UnionWith(threadIds);
Console.WriteLine("Current thread count: {0}; Cumulative thread count: {1}.", threadIds.Count, distinctThreadIds.Count);
Thread.Sleep(250);
}
testTask.Wait();
Console.WriteLine(timer.Elapsed);
Console.ReadLine();
}
}
}
此代码尝试运行100 HttpClient.GetStringAsync
个任务,向网站发出请求,需要1分钟才能响应。同时,它计算活动线程的数量和进程创建的不同累积数量。正如我所预测的,这个程序会创建许多新线程。输出如下:
Current thread count: 4; Cumulative thread count: 4.
....
Current thread count: 25; Cumulative thread count: 25.
....
Current thread count: 7; Cumulative thread count: 63.
Current thread count: 9; Cumulative thread count: 65.
00:10:01.9981006
这意味着:
答案 0 :(得分:16)
将方法标记为异步意味着我们可以使用await关键字基本上在另一个线程上安排繁重的处理并释放UI线程,直到处理完成。
这根本不是async
的工作方式。查看我的async
intro。
你可能会说* Async方法使用一些特殊的魔法(比如处理外部信号),使它们比同步方法更有效。问题是,我不认为这是事实。
在纯异步代码中,there is no thread(正如我在博客中解释的那样)。实际上,在设备驱动程序级别,所有(非平凡)I / O是异步的。它是同步API(在操作系统级别),是自然异步API的抽象层。
让我们看一下Stream.ReadAsync。
Stream
是一个不寻常的案例。作为基类,它必须尽可能地防止破坏性更改。因此,当他们添加虚拟ReadAsync
方法时,他们必须添加默认实现。此实现必须使用非理想的实现(Task.Run
),这是不幸的。在一个理想的世界中,ReadAsync
将是(或调用)一个抽象的异步实现,但这会破坏Stream
的每个现有实现。
有关更恰当的示例,请比较WebClient
和HttpClient
之间的差异。
答案 1 :(得分:5)
让我们进行真实的测试:自然异步WebRequest.GetResponseAsync
与非自然同步WebRequest.GetResponse
。
首先,我们扩展ThreadPool
:
ThreadPool.SetMaxThreads(MAX_REQS * 2, MAX_REQS * 2);
ThreadPool.SetMinThreads(MAX_REQS, MAX_REQS);
注意我请求相同数量的workerThreads
和completionPortThreads
。然后,我们将使用每个API向bing.com执行MAX_REQS
= 200个并行请求。
代码(独立控制台应用):
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using System.Net;
namespace Console_21690385
{
class Program
{
const int MAX_REQS = 200;
// implement GetStringAsync
static async Task<string> GetStringAsync(string url)
{
using (var response = await WebRequest.Create(url).GetResponseAsync())
using (var stream = response.GetResponseStream())
using (var reader = new System.IO.StreamReader(stream))
{
return await reader.ReadToEndAsync();
}
}
// test using GetStringAsync
static async Task TestWithGetStringAsync()
{
var tasks = Enumerable.Range(1, MAX_REQS).Select((i) =>
GetStringAsync("http://www.bing.com/search?q=item1=" + i));
Console.WriteLine("Threads before completion: " + Process.GetCurrentProcess().Threads.Count);
await Task.WhenAll(tasks);
Console.WriteLine("Threads after completion: " + Process.GetCurrentProcess().Threads.Count);
}
// implement GetStringSync
static string GetStringSync(string url)
{
using (var response = WebRequest.Create(url).GetResponse())
using (var stream = response.GetResponseStream())
using (var reader = new System.IO.StreamReader(stream))
{
return reader.ReadToEnd();
}
}
// test using GetStringSync
static async Task TestWithGetStringSync()
{
var tasks = Enumerable.Range(1, MAX_REQS).Select((i) =>
Task.Factory.StartNew(
() => GetStringSync("http://www.bing.com/search?q=item1=" + i),
CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default));
Console.WriteLine("Threads before completion: " + Process.GetCurrentProcess().Threads.Count);
await Task.WhenAll(tasks);
Console.WriteLine("Threads after completion: " + Process.GetCurrentProcess().Threads.Count);
}
// run either of the tests
static void RunTest(Func<Task> runTest)
{
Console.WriteLine("Threads at start: " + Process.GetCurrentProcess().Threads.Count);
var stopWatch = new Stopwatch();
stopWatch.Start();
var testTask = runTest();
while (!testTask.IsCompleted)
{
Console.WriteLine("Currently threads: " + Process.GetCurrentProcess().Threads.Count);
Thread.Sleep(1000);
}
Console.WriteLine("Threads at end: " + Process.GetCurrentProcess().Threads.Count + ", time: " + stopWatch.Elapsed);
testTask.Wait();
}
static void Main(string[] args)
{
ThreadPool.SetMaxThreads(MAX_REQS * 2, MAX_REQS * 2);
ThreadPool.SetMinThreads(MAX_REQS, MAX_REQS);
Console.WriteLine("Testing using GetStringAsync");
RunTest(TestWithGetStringAsync);
Console.ReadLine();
Console.WriteLine("Testing using GetStringSync");
RunTest(TestWithGetStringSync);
Console.ReadLine();
}
}
}
<强>输出:强>
Testing using GetStringAsync
Threads at start: 3
Threads before completion: 3
Currently threads: 25
Currently threads: 84
Currently threads: 83
Currently threads: 83
Currently threads: 83
Currently threads: 83
Currently threads: 83
Currently threads: 84
Currently threads: 83
Currently threads: 83
Currently threads: 84
Currently threads: 84
Currently threads: 84
Currently threads: 83
Currently threads: 83
Currently threads: 84
Currently threads: 83
Currently threads: 82
Currently threads: 82
Currently threads: 82
Currently threads: 83
Currently threads: 25
Currently threads: 25
Currently threads: 26
Currently threads: 25
Currently threads: 25
Currently threads: 25
Currently threads: 23
Currently threads: 23
Currently threads: 24
Currently threads: 20
Currently threads: 20
Currently threads: 19
Currently threads: 19
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 19
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 18
Currently threads: 18
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 19
Currently threads: 19
Currently threads: 18
Currently threads: 18
Currently threads: 17
Threads after completion: 17
Threads at end: 17, time: 00:00:51.2605879
Testing using GetStringSync
Threads at start: 15
Threads before completion: 15
Currently threads: 55
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 213
Currently threads: 212
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 210
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 209
Currently threads: 205
Currently threads: 201
Currently threads: 196
Currently threads: 190
Currently threads: 186
Currently threads: 182
Threads after completion: 178
Threads at end: 173, time: 00:00:47.2603652
结果:
两个测试都需要大约50秒才能完成,但GetStringAsync
在83个线程处达到峰值,而GetStringSync
在213处达到峰值。MAX_REQS
数字越高,浪费的线程就越多阻止WebRequest.GetResponse
API。
@Ark-kun,我希望你现在明白这一点。