“新线程”和异步

时间:2015-12-30 06:56:07

标签: c# multithreading asynchronous async-await

我使用下面的代码做了一个模拟并发的示例:

var threads = new Thread[200]; 
//starting threads logic
for (int i = 0; i < 200; i++)
         {
             threads[i].Start();
         }
         for (int i = 0; i < 200; i++)
         {
             threads[i].Join();
         } 

代码应该向数据库插入数千条记录,它似乎运行良好,因为线程几乎同时完成。

但是,当我使用:

 var tasks = new List<Task<int>>();
        for (int i = 0; i < 200; i++)
        {
            tasks.Add(insert(i));
            //   await insert(i);
        }
        int[] result = await Task.WhenAll(tasks);

完成相同的逻辑需要花费大量时间。

有人可以向我解释有什么区别吗?我认为Await应该创建线程。

2 个答案:

答案 0 :(得分:1)

如果您需要复制原始基于Thread的行为,则可以使用Task.Factory.StartNew(... , TaskCreationOptions.LongRunning)来安排工作,然后阻止,直到通过Task.WaitAll完成工作人员任务。我不推荐这种方法,但就行为而言,这将非常接近您的代码之前的工作方式。

有关为何可能无法在您的方案中获得预期效果的更深入分析如下:

解释,第1部分async并不意味着“在不同的线程上”)

标有async关键字的方法不会神奇地异步运行。他们只是能够将等待的操作(可能会或可能不会异步运行)组合成一个更大的单元(通常是TaskTask<T>)。

如果您的insert方法为async,则它仍然可能同步执行至少某些工作。在第一个await语句之前,代码的所有肯定会出现这种情况。这项工作将在“主”线程(调用insert的线程)上执行 - 这将是你的瓶颈或至少是其中的一部分,因为你的代码段的并行度将为1无论你是insert生成的任务,都在紧密循环中调用await

为了说明上述观点,请考虑以下示例:

void Test()
{
    Debug.Print($"Kicking off async chain (thread {Thread.CurrentThread.ManagedThreadId}) - this is the main thread");
    OuterTask().Wait(); // Do not block on Tasks - educational purposes only.
}

async Task OuterTask()
{
    Debug.Print($"OuterTask before await (thread {Thread.CurrentThread.ManagedThreadId})");
    await InnerTask().ConfigureAwait(false);
    Debug.Print($"OuterTask after await (thread {Thread.CurrentThread.ManagedThreadId})");
}

async Task InnerTask()
{
    Debug.Print($"InnerTask before await (thread {Thread.CurrentThread.ManagedThreadId})");
    await Task.Delay(10).ConfigureAwait(false);
    Debug.Print($"InnerTask after await (thread {Thread.CurrentThread.ManagedThreadId}) - we are now on the thread pool");
}

这会产生以下输出:

Kicking off async chain (thread 6) - this is the main thread
OuterTask before await (thread 6)
InnerTask before await (thread 6)
InnerTask after await (thread 8) - we are now on the thread pool
OuterTask after await (thread 8)

请注意,{main} await内的第一个Task1以及Task2之前的代码仍在“主”线程上执行。我们的链实际上是在启动外部任务的同一个线程上同步执行,直到我们await第一次真正的异步操作(在这种情况下为Task.Delay)。

另外

如果您在SynchronizationContext.Current非空(即Windows窗体,WPF)的环境中运行,并且您未在ConfigureAwait(false)方法中等待的任务上使用insert, 第一个await语句之后,异步状态机安排的延续将可能在“主”线程上执行 - 尽管在某些环境中无法保证这一点(即ASP.NET)。

解释,第2部分(在线程池上执行Task

如果作为insert方法的一部分,您选择手动启动任何Task,那么您最有可能使用Task.Run或者TaskCreationOptions.LongRunning来安排您在线程池上的工作启动未指定IEnumerable<Task> tasks = Enumerable .Range(0, 200) .Select(_ => Task.Run(() => Thread.Sleep(100))); // Using Thread.Sleep to simulate blocking calls. await Task.WhenAll(tasks); // Completes in 2+ seconds. 的新任务的任何其他方法。一旦线程池达到饱和状态,任何新启动的任务都将排队,从而降低并行系统的吞吐量。

证明:

TaskCreationOptions.LongRunning

现在使用IEnumerable<Task> tasks = Enumerable .Range(0, 200) .Select(_ => Task.Factory.StartNew( () => Thread.Sleep(100), TaskCreationOptions.LongRunning )); await Task.WhenAll(tasks); // Completes in under 130 milliseconds.

import Data.Bool (bool)
--         F    T    
-- bool :: a -> a -> Bool -> a

ensure :: (a -> Bool) -> a -> Maybe a
ensure p x = bool (const Nothing) Just (p x) x

ensure p   = join (bool (const Nothing) Just . p)
           = bool (const Nothing) Just =<< p  

ensure     = (bool (const Nothing) Just =<<)

通常一个好主意产生200个线程(这不会很好地扩展),但如果阻塞调用的大规模并行化是一个绝对的要求,上面的代码片段向您展示了一种方法它与TPL。

答案 1 :(得分:0)

在第一个示例中,您手动创建了线程。第二,你创建了任务。任务 - 可能 - 正在使用线程池,其中存在有限的线程数。因此,大多数任务都在队列中等待,而其中很少有人在可用线程上并行执行。