C# 如果能够使用Task.run异步运行任何方法,那么.Net Framework中的*异步方法的用途是什么?
简短问题: 为什么.Net Framework添加了很多*异步版本的方法,而不是开发人员只使用C# 如果能够使用Task.run异步运行任何方法,那么.Net Framework中的*异步方法的用途是什么?,c#,.net,multithreading,asynchronous,async-await,C#,.net,Multithreading,Asynchronous,Async Await,简短问题: 为什么.Net Framework添加了很多*异步版本的方法,而不是开发人员只使用Task.Run异步运行同步方法 详细问题: 我理解异步的概念 我了解任务 我知道async/await关键字 我知道.Net Framework中的*异步方法做什么 我不明白的是库中*异步方法的用途 假设您有两行代码: F1(); F2(); 关于数据/控制流,只有两种情况: F2需要在F1完成后执行 F2无需等待F1完成 我没有看到任何其他案例。我不认为有任何需要知道执行某些功能(除了UI
Task.Run
异步运行同步方法
详细问题:
- 我理解异步的概念
- 我了解
任务
- 我知道async/await关键字
- 我知道.Net Framework中的*异步方法做什么
F1();
F2();
关于数据/控制流,只有两种情况:
需要在F2
完成后执行F1
无需等待F2
完成F1
F1
的工作负载很小时,这种差异并不重要。但是当A需要很多时间才能完成时,我们可能需要查看情况,如果F2
不需要等待F1
完成,我们可以与F2
并行运行F1
很久以前,我们使用线程/线程池实现了这一点。现在我们有了任务
如果我们想并行运行F1
和F2
,我们可以编写:
var task1 = Task.Run(F1);
F2();
任务很酷,我们可以在最终需要完成任务的地方使用wait
到目前为止,我认为没有必要创建F1Async()
方法
现在,让我们来看一些特殊情况。
我看到的唯一真正的特例是UI。UI线程是特殊的,它会使UI冻结,这是很糟糕的。
在我看来,Microsoft建议我们将UI事件处理程序标记为异步。标记方法async
意味着我们可以使用wait
关键字基本上在另一个线程上安排繁重的处理,并释放UI线程,直到处理完成
我不明白的是,为什么我们需要任何*异步方法来等待它们。我们总是可以编写等待任务。运行(F1)代码>。为什么我们需要F1Async
您可以说*异步方法使用了一些特殊的魔法(比如处理外部信号),使它们比同步方法更有效。问题是我不认为这是事实
例如,让我们看一下流.ReadAsync
。如果您查看源代码,ReadAsync
只是浪费了几百行代码来创建一个只调用synchronousRead
方法的任务。那我们为什么需要它呢?为什么不直接使用任务。使用流运行。读取
这就是为什么我不理解通过创建同步方法的平凡*异步副本来扩充库的必要性。MS甚至可以添加语法糖,这样我们就可以编写await async Stream.Read
而不是await Stream.ReadAsync
或Task.Run(Stream.Read)
现在您可能会问“为什么不让*异步方法成为唯一的方法,并删除同步方法?”。如前所述,基本代码执行模式是同步的。异步运行同步方法很容易,但另一种方式不行
那么,如果能够使用Task.run异步运行任何方法,.Net Framework中的*Async方法的用途是什么?
另外,如果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();
}
}
}
这意味着:
- 在异步任务执行过程中创建了61个新线程
- 新活动线程的峰值数为21
- 执行时间增加了10倍(10分钟而不是1分钟)。这是由本地IIS限制造成的
将方法标记为async意味着我们可以使用await关键字基本上在另一个线程上调度繁重的处理,并释放UI线程,直到处理完成
这根本不是async
的工作方式。看我的
您可以说*异步方法使用了一些特殊的魔法(比如处理外部信号),使它们比同步方法更有效。问题是我不认为这是事实
在纯异步代码中(正如我在博客上解释的)。事实上,在设备驱动程序级别,所有(非平凡)I/O都是异步的。同步API(在操作系统级别)是自然异步API之上的抽象层
例如,让我们看一下Stream.ReadAsync
流
是一种不寻常的情况。作为基类,它必须尽可能防止破坏更改。因此,当他们添加virtualReadAsync
方法时,他们必须添加一个默认实现。此实现必须使用非理想的实现(Task.Run
),这是不幸的。在理想情况下,ReadAsync
将是(或调用)抽象异步的
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
ThreadPool.SetMaxThreads(MAX_REQS * 2, MAX_REQS * 2);
ThreadPool.SetMinThreads(MAX_REQS, MAX_REQS);
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();
}
}
}