C# 如何将其转换为异步任务?

C# 如何将其转换为异步任务?,c#,.net,.net-4.5,async-await,c#-5.0,C#,.net,.net 4.5,Async Await,C# 5.0,给定以下代码 static void DoSomething(int id) { Thread.Sleep(50); Console.WriteLine(@"DidSomething({0})", id); } 我知道我可以将其转换为异步任务,如下所示 static async Task DoSomethingAsync(int id) { await Task.Delay(50); Console.WriteLine(@"DidSomethingAsync({

给定以下代码

static void DoSomething(int id) {
    Thread.Sleep(50);
    Console.WriteLine(@"DidSomething({0})", id);
}
我知道我可以将其转换为异步任务,如下所示

static async Task DoSomethingAsync(int id) {
    await Task.Delay(50);
    Console.WriteLine(@"DidSomethingAsync({0})", id);
}
如果我多次调用(Task.whalll),那么这样做会比使用Parallel.Foreach甚至从循环中调用更快、更高效

但有一分钟,让我们假设Task.Delay()不存在,实际上我必须使用Thread.Sleep();我知道事实并非如此,但这是概念代码,延迟/睡眠通常是IO操作,没有异步选项(如早期EF)

我试过以下方法

static async Task DoSomethingAsync2(int id) {
    await Task.Run(() => {
        Thread.Sleep(50);
        Console.WriteLine(@"DidSomethingAsync({0})", id);
    });
}
static async Task DoSomethingAsync3(int id) {
    await new Task(() => {
        Thread.Sleep(50);
        Console.WriteLine(@"DidSomethingAsync({0})", id);
    });
}
但是,尽管它运行时没有错误,但根据这一点,这实际上是一种糟糕的做法,因为它只是从池中旋转线程来完成每个任务(使用以下控制台应用程序的速度也较慢-如果在DoSomethingAsync和DoSomethingAsync2调用之间进行交换,则可以看到完成调用所需的时间有显著差异)

将其移植到原来的DoSomethingAsync中,测试永远不会完成,屏幕上也不会显示任何内容

我还尝试了其他多种变体,它们要么无法编译,要么无法完成

因此,考虑到不能调用任何现有异步方法并且必须在异步任务中同时完成Thread.Sleep和Console.WriteLine的约束,您如何以与原始代码一样高效的方式完成该任务


对于那些感兴趣的人来说,这里的目标是让我更好地理解如何创建我自己的异步方法,而我不调用任何其他人。尽管进行了很多搜索,但这似乎是一个真正缺乏示例的领域,而调用调用其他asyn的异步方法的示例有成千上万c方法反过来,我找不到任何将现有void方法转换为异步任务的方法,在异步任务中,除了那些使用task.Run(()=>{})方法的任务外,没有其他异步任务的调用。

有两种任务:执行代码的任务(例如,
task.Run
和friends),以及响应某些外部事件的任务(例如,
TaskCompletionSource
和朋友)

你要找的是
TaskCompletionSource
。有各种“速记”用于常见情况的表单,因此您不必总是直接使用
TaskCompletionSource
。例如,
Task.FromResult
TaskFactory.FromAsync
FromAsync
是最常用的,如果您有一个现有的
*开始
/
*结束
I/O实现,则可以使用
>TaskCompletionSource直接输入

有关更多信息,请参阅的“I/O绑定任务”部分

任务
构造函数(不幸的是)是基于任务的并行性的保留,不应在异步代码中使用。它只能用于创建基于代码的任务,而不是外部事件任务

因此,考虑到不能调用任何现有异步方法并且必须在异步任务中同时完成Thread.Sleep和Console.WriteLine的约束,您如何以与原始代码一样高效的方式完成该任务

我会使用某种计时器,并在计时器启动时让它完成一个
TaskCompletionSource
。我几乎可以肯定,这就是实际的
任务。延迟
实现的作用

因此,考虑到不能调用任何现有 异步方法,并且必须同时完成Thread.Sleep和 在异步任务中,如何在 与原始代码一样高效的方式

在我看来,这是一个非常综合的约束,您确实需要坚持使用
线程。Sleep
。在这个约束下,您仍然可以稍微改进基于
线程。Sleep
的代码。取而代之的是:

static async Task DoSomethingAsync2(int id) {
    await Task.Run(() => {
        Thread.Sleep(50);
        Console.WriteLine(@"DidSomethingAsync({0})", id);
    });
}
您可以这样做:

static Task DoSomethingAsync2(int id) {
    return Task.Run(() => {
        Thread.Sleep(50);
        Console.WriteLine(@"DidSomethingAsync({0})", id);
    });
}
这样可以避免编译器生成的状态机类的开销

无论如何,这不是减速的瓶颈所在

(使用以下控制台应用程序也会变慢-如果 在DoSomethingAsync和DoSomethingAsync2调用之间交换,您可以看到 完成所需时间的显著差异)

让我们再看一次主循环代码:

static async Task MainAsync(String[] args) {

    List<Task> tasks = new List<Task>();
    for (int i = 1; i <= 1000; i++)
        tasks.Add(DoSomethingAsync2(i)); // Can replace with any version
    await Task.WhenAll(tasks);

}
输出:

Press enter to test with Task.Delay ... ... time, ms: 1077, threads at peak: 13 Press enter to test with Thread.Sleep ... ... time, ms: 8684, threads at peak: 21 Press enter to test with Thread.Sleep ... ... time, ms: 3600, threads at peak: 163 输出:

Press enter to test with Task.Delay ... ... time, ms: 1077, threads at peak: 13 Press enter to test with Thread.Sleep ... ... time, ms: 8684, threads at peak: 21 Press enter to test with Thread.Sleep ... ... time, ms: 3600, threads at peak: 163
上一个示例从未完成,因为您从未对所创建的任务调用
Start
。@lee:如果我等待新任务(()=>{}),则Start()不会编译,因为task.Start()返回void。如果您需要在不阻塞线程的情况下高效地等待,则可以使用它。您需要在尝试
等待它之前启动任务。
var task=new task(…);task.start();wait task;
@lee:还尝试了静态异步任务DoSomethingAsync2(int id){task task=new task(()=>{Thread.Sleep(50);Console.WriteLine(@“DidSomethingAsync({0})”,id);});task.Start();wait task;}但这和DoSomethingAsync2示例一样慢。谢谢,但您对计时器和TaskCompletionSource的引用可能没有抓住要点;Thread.Sleep只是我想在没有异步方法调用的情况下完成的长时间运行任务的一个示例。也许我应该用长时间运行的循环来代替它,以获得更多clari马丁·罗宾斯:问题是“是什么让你的任务长期运行?”?如果它实际上正在运行代码,则正确的解决方案是同步执行它或使用
Task。如果要在线程池线程上运行它,请运行
。如果它是基于I/O的,或是由于外部事件而完成的,则使用
TaskFactory.fromsync
TaskCompletionSource
。没有其他方法你能详细说明一下吗 Press enter to test with Thread.Sleep ... ... time, ms: 3600, threads at peak: 163
Task.Factory.StartNew(() =>
{
    Thread.Sleep(1000);
    Console.WriteLine("Thread pool: " + 
        Thread.CurrentThread.IsThreadPoolThread); // false!
}, TaskCreationOptions.LongRunning).Wait();