代码之家  ›  专栏  ›  技术社区  ›  user7127000

如果我想实现这个线程概念,我应该如何使用TPL?

  •  2
  • user7127000  · 技术社区  · 9 年前

    假设我有一种方法,比如 SaveAsync(Item item) 我需要在10点打电话 Item s和调用彼此独立。我认为线程的理想方式是 喜欢

    Thread A | Run `SaveAsync(item1)` until we hit the `await` | ---- ... ---- | Run `SaveAsync(item10)` until we hit the `await` | ---------------------------------------|
    Thread B | --------------------------------------------------- | Run the stuff after the `await` in `SaveAsync(item1)` | ------------------ ... -----------------------|  
    Thread C | ------------------------------------------------------ | Run the stuff after the `await` in `SaveAsync(item2)` | ------------------ ... --------------------|  
    .
    .
    .                                                                 
    

    (有可能在 await

    我想知道怎么用C写?它是一个并行foreach还是一个带有wait的循环 SaveAsync(item)

    2 回复  |  直到 9 年前
        1
  •  2
  •   Iqon    9 年前

    按默认值 async 任务将始终返回到其启动的线程上下文。您可以通过添加

    await task.ConfigureAwait(false)
    

    这允许告诉运行时,您不在乎任务将在哪个线程上下文上恢复,并且运行时可以忽略当前线程上下文的捕获(这相当昂贵)。

    但是,默认情况下,您将始终在启动任务的线程上下文上进行调度。

    在线程池上下文上启动的任务将被调度到池中的下一个空闲线程。不一定是启动任务的同一个线程。

    但是,如果需要对任务调度进行更多控制,则可以提供自己的上下文。

    foreach(var item in items)
    {
        await SaveAsync(item);
    }
    

    这个 await 这里将等到 SaveAsync 饰面。因此,所有保存都是按顺序处理的。

    诀窍是启动所有任务,但不要等待它们,直到所有任务都启动。那你呢 wait 具有的所有任务 WhenAll(IEnumerable<Task>) .

    var tasks = new List<Task>();
    foreach(var item in items)
    {
        tasks.Add(SaveAsync(item)); // No await here
    }
    
    await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)
    

    由于缺少等待,所有“保存操作”都放在异步/等待状态机中。一旦第一个任务返回,第二个任务将被执行。这将导致与问题中描述的行为有点类似的行为。

    这里唯一的主要区别是,所有任务都在同一个线程中执行。这在大多数情况下是完整的,因为所有 Save 方法通常需要访问相同的资源。将它们并行化并没有带来任何真正的优势,因为瓶颈是这种资源。

    如何使用多线程

    Task.Run(SaveAsync(item));
    

    要在不同的线程上执行所有项目,可以使用与之前几乎相同的代码:

    var tasks = new List<Task>();
    foreach(var item in items)
    {
        tasks.Add(Task.Run(SaveAsync(item));); // No await here
    }
    
    await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)
    

    唯一的区别是,我们采用taks返回的表格 StartNew .

    使用 Task.Run 并不保证你有一个新的线程。它将在线程池中的下一个空闲线程上执行任务。这取决于您的本地设置以及本地配置(例如,一个笨重的裸体服务器将比任何消费者笔记本电脑拥有更多的线程)。

    无论您是获得一个新线程,还是必须等待任何被占用的线程完成,都完全取决于线程池。(tread池通常做得非常好。有关更多信息,请参阅这篇关于线程池性能的非常好的文章: CLR-Thread-Pool )

        2
  •  1
  •   ipavlu    9 年前

    这就是人们在使用async/await时犯的大多数错误:

    2) 或者人们认为异步确实是同步运行的。

    事实是介于和之间的某个地方@Iqon关于下一个代码块的陈述实际上是不正确的: “这里唯一的主要区别是,所有任务都在同一个线程中执行。”

    var tasks = new List<Task>();
    foreach(var item in items)
    {
        tasks.Add(SaveAsync(item)); // No await here
    }
    
    await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)
    

    以下是示例:

    async Task SaveAsync1(Item item)
    {
        //no awaiting at all
    }   
    
    async Task SaveAsync2(Item item)
    {
        //awaiting already completed task
        int i = await Task.FromResult(0);
    }   
    

    像这样的方法实际上可以在异步任务执行的线程上同步运行。但这些异步方法是特殊的雪花。这里没有等待的操作,即使await在方法中,一切都是逗号,因为它在第一种情况下不等待,在第二种情况下,它在完成任务时等待,所以在await之后它会同步继续,这两个调用将是相同的:

    var taskA = SaveAsync2(item);//it would return task runned to completion
    
    //same here, await wont happen as returned task was runned to completion
    await SaveAsync2(item);
    

    因此,在这里同步执行异步方法只在这种特殊情况下是正确的:

    var tasks = new List<Task>();
    foreach(var item in items)
    {
        tasks.Add(SaveAsync2(item));
    }
    
    await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)
    

    foreach(var item in items)
    {
        SaveAsync2(item);
        //it will execute synchronously because there is
        //nothing to await for in the method
    }
    

    现在,让我们探索真实情况,一种实际等待内部某物或触发可等待操作的异步方法:

    async Task SaveAsync3(Item item)
    {
        //awaiting already completed task
        int i = await Task.FromResult(0);
    
        await Task.Delay(1000);
        Console.WriteLine(i);
    }
    

    这会有什么作用?

    var tasks = new List<Task>();
    foreach(var item in items)
    {
        tasks.Add(SaveAsync3(item));
    }
    
    await Task.WhenAll(tasks); // will only continue when all tasks are finished (or cancelled or failed)
    

    它会并行运行吗?不!

    那么代码做了什么?它同步执行每个SaveAsync3,直到等待任务。延迟,它发现返回的任务不完整并返回给调用方,并提供了不完整的任务,该任务存储到任务中,下一个SaveAsync以相同的方式执行。

    现在等待任务。WhenAll(任务);这真的很有意义,因为它正在等待一些不完整的操作,这些操作将在这个线程上下文之外并行运行。 等待任务。延迟 将调度到线程池并并行运行,除非需要特殊情况,如UI线程上下文,在这种情况下,需要在TaskDelay之后配置等待(false)。

    它经常被误用,我认为实际上只有两种主要情况:

    1) 当你想要脱离当前线程上下文时,比如UI,ASP。净等

    一、