【C#】51. Await 处理并行任务(WhenAll)以及Task.Delay()

这篇文章主要说明两个问题:1、await如何替代ContinueWith来处理WhenAll之类的并行多任务;2、Task.Delay()的原理。

async static Task<string> GetInfoAsync(string name, int seconds)
{
await Task.Delay(TimeSpan.FromSeconds(seconds));
//await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(seconds)));
return string.Format("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
}

async static Task AsynchronousProcessing()
{
Task<string> t1 = GetInfoAsync("Task 1", 3);
Task<string> t2 = GetInfoAsync("Task 2", 5);
string[] results = await Task.WhenAll(t1, t2);
foreach (string result in results)
{
Console.WriteLine(result);
}
}


主线程函数:

Task t = AsynchronousProcessing();
t.Wait();
Console.Read();



任务t1和t2构成的WhenAll任务使用await直接返回结果给string[] results,由于是异步的关系,因此总共等待5秒左右,两个任务都完成。

仔细的话可能会发现,明明是两个线程执行异步操作,为什么线程池线程却用的是同一个?(以下为书中原文)

原因在于Task.Delay在幕后使用了一个计时器,从线程池中获取工作线程,将得带Task.Delay方法返回结果,然后,Task.Delay方法启动计时器并且指定一块代码,该代码会在计时器时间到了Task.Delay方法中指定的时间后被调用。之后立即将工作线程返回到线程池。当计时器时间运行时,我们线程池中任意获取一个可用的工作线程(可能就是运行一个任务时使用的线程)并运行计时器提供给他的代码。

好了,接下来我们来试试其他两种阻塞线程的方法:

1)

await Task.Run(()=> Thread.Sleep(TimeSpan.FromSeconds(seconds)));
这种方法将使得线程(线程池中线程)阻塞指定时间,然后继续执行余下代码。但是请注意,由于是开了两个线程池线程,他们在各自的线程中阻塞,因此运行时间还是5秒钟

2)

Thread.Sleep(TimeSpan.FromSeconds(seconds));
这种方法非常直接,但是却是最糟糕的!因为并没有开线程,而是直接使用了主线程来执行阻塞,由于在阻塞期间不能做任何事情(因为主线程被阻塞,不能要求主线程再去执行其他异步或者非异步操作!),因此实际上这个程序运行了8秒钟


C# 中,虽然单线程无法直接实现真正的并行处理,因为它的名字“Single Threaded”就表明了它一次只能运行在一个处理器核心上,但是通过一些技巧,可以模拟某种程度的并行效果。例如,你可以使用 `Task` 或 `Thread` 类以及异步编程模型(如 `async` 和 `await` 关键字),结合 `lock` 和 `Monitor` 对象来管理共享资源的并发访问,避免数据竞争。 下面是一个简单的例子,使用 `Task.Run` 创建一个任务,让代码在一个单独的线程上运行,看起来像是并行处理: ```csharp using System; using System.Threading.Tasks; public class SingleThreadParallelExample { private static object _lock = new object(); public void PerformCalculations(int[] numbers) { var tasks = new Task[int] { }; for (int i = 0; i < numbers.Length; i++) { int number = numbers[i]; tasks[i] = Task.Run(() => ProcessNumber(number)); } // 等待所有任务完成,顺序执行 await Task.WhenAll(tasks); Console.WriteLine("All calculations finished."); } private async void ProcessNumber(int number) { lock (_lock) { Console.WriteLine($"Processing number {number}..."); // 这里是模拟并行工作的假象,实际还是串行 // 进行耗时操作... await Task.Delay(500); // 模拟计算时间 } } public static void Main() { var numbers = Enumerable.Range(0, 10).ToArray(); var example = new SingleThreadParallelExample(); example.PerformCalculations(numbers); } } ``` 在这个示例中,`ProcessNumber` 方法会被逐个放入独立的任务中执行,但实际上由于在同一全局锁 `_lock` 的控制下,它们是串行化的。这是为了演示如何在单线程中模拟并行,如果需要真正的并行处理,应该考虑使用 `Parallel.ForEach`、`Task Parallel Library (TPL)` 或 `ThreadPool`。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值