2013-05-29 33 views
1

我试图在已经存在的非常大的同步代码库中使用async/await。在这个代码库中有一些全局状态,如果kludgy在同步上下文中工作正常,但它在async/await的异步上下文中不起作用。使用异步/等待,什么时候连续发生?

所以,我的两个选择似乎是两种因素走出全球范围内其woould是一个非常大和非常耗时的任务,或者做一些巧妙的搭配延续时运行。

为了更好地理解异步/等待和延续,我做了一个测试程序,如下图所示。显示在这里。

// A method to simulate an Async read of the database. 
    private static Task ReadAsync() 
    { 
     return Task.Factory.StartNew(() => 
     { 
      int max = int.MaxValue/2; 
      for (int i = 0; i < max; ++i) 
      { 
      } 
     }); 
    } 

    // An async method that creates several continuations. 
    private static async Task ProcessMany(int i) 
    { 
     Console.WriteLine(string.Format("{0} {1}", i.ToString(), 0)); 

     await ReadAsync(); 

     Console.WriteLine(string.Format("{0} {1}", i.ToString(), 1)); 

     await ReadAsync(); 

     Console.WriteLine(string.Format("{0} {1}", i.ToString(), 2)); 

     await ReadAsync(); 

     Console.WriteLine(string.Format("{0} {1}", i.ToString(), 3)); 
    } 


    public static void Main(string[] args) 
    { 
     Queue<Task> queue = new Queue<Task>(); 

     for (int i = 0; i < 10; ++i) 
     { 
      queue.Enqueue(ProcessMany(i)); 
     } 

     // Do some synchonous processing... 
     Console.WriteLine("Processing... "); 

     for (int i = 0; i < int.MaxValue; ++i) 
     { 
     } 

     Console.WriteLine("Done processing... "); 

     queue.Dequeue().Wait(); 
    } 

阅读所有关于异步后/ AWAIT,我的理解是,没有一个延续的会之间发生“处理..”和“处理完毕后......” WriteLines。

以下是一些示例输出。

0 0 
1 0 
2 0 
3 0 
4 0 
5 0 
6 0 
7 0 
8 0 
9 0 
Processing... 
3 1 
2 1 
7 1 
6 1 
0 1 
4 1 
5 1 
1 1 
6 2 
3 2 
Done processing... 
7 2 
2 2 
0 2 
4 2 
5 2 
1 2 
6 3 
3 3 
7 3 
2 3 
0 3 

我希望单等待()在程序结束时,而第一个完成可能产生多个延续,但我不知道如何延续的任何可能“处理之间运行.. 。“和”完成处理...“。我认为在Console.WriteLine方法中可能存在yield或者某种东西,所以我完全替换了它,但是这并没有改变输出。

很明显我在异步的理解差距/等待。当我们只是递增一个变量时,怎么会发生延续?编译器还是CLR在这里注入某种魔力?

预先感谢您的任何帮助更好地理解异步/等待和延续。

编辑:

如果这样按照斯蒂芬注释编辑示例代码,这是怎么回事更加明显。

// An async method that creates several continuations. 
    private static async Task ProcessMany(int i) 
    { 
     Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 0, Thread.CurrentThread.ManagedThreadId)); 

     await ReadAsync(); 

     Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 1, Thread.CurrentThread.ManagedThreadId)); 

     await ReadAsync(); 

     Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 2, Thread.CurrentThread.ManagedThreadId)); 

     await ReadAsync(); 

     Console.WriteLine(string.Format("{0} {1} {2}", i.ToString(), 3, Thread.CurrentThread.ManagedThreadId)); 
    } 


    public static void Main(string[] args) 
    { 
     Queue<Task> queue = new Queue<Task>(); 

     for (int i = 0; i < 10; ++i) 
     { 
      queue.Enqueue(ProcessMany(i)); 
     } 

     // Do some synchonous processing... 
     Console.WriteLine("Processing... {0}", Thread.CurrentThread.ManagedThreadId); 

     for (int i = 0; i < int.MaxValue; ++i) 
     { 
     } 

     Console.WriteLine("Done processing... {0}", Thread.CurrentThread.ManagedThreadId); 

     queue.Dequeue().Wait(); 
    } 

输出:

0 0 9 
1 0 9 
2 0 9 
3 0 9 
4 0 9 
5 0 9 
6 0 9 
7 0 9 
8 0 9 
9 0 9 
Processing... 9 
4 1 14 
3 1 13 
2 1 12 
5 1 15 
0 1 10 
6 1 16 
1 1 11 
7 1 17 
4 2 14 
3 2 13 
0 2 10 
6 2 16 
2 2 12 
5 2 15 
Done processing... 9 
1 2 11 
7 2 17 
0 3 10 
4 3 14 

回答

5

如果没有当前SynchronizationContextTaskScheduler,则延续将在一个线程池线程执行(独立于主线程)。在控制台应用程序中就是这种情况,但在WinForms/WPF/ASP.NET中您会看到非常不同的行为。

虽然你通过使用自定义TaskScheduler,那会是相当多的工作与可能收效甚微可能控制延续调度。我不清楚您的全球状态存在哪些问题,但请考虑替代方案,如SemaphoreSlim

+0

谢谢,我几乎在你发布的同一时间就意识到了这一点。我编辑了示例代码,现在它变得更有意义。 – Ashley

+0

所以,这意味着在非UI上下文中,继续必须是线程安全的?我认为异步/等待的巨大好处意味着您不必担心线程安全性,因为它将全部在主线程上运行。 :\ – Ashley

+0

我的全局状态与数据库连接和数据库事务有关,它们以非常复杂的方式编入代码库,然后解开它将会非常困难。这是一个30岁的代码库,已经通过多个端口转向新语言。 :) – Ashley

0

只要您拨打下面的行放在ProcessMany,每次调用ProcessMany开始在一个单独的线程在一个单独的线程池马上执行。

await ...; 

所以这就是为什么你看到一堆你的“处理”打印之前调用。所以,当你完成所有这10个ProcessMany调用时,你就开始运行你的大循环。由于大循环正在主线程上运行,因此10个ProcessMany调用将继续在其线程中执行,从而产生额外的打印输出。看起来你的ProcessMany调用在你的主线程循环之前没有完成执行,所以他们在你的“完成处理”打印输出后继续吐出更多的结果。

我希望能够澄清事物的顺序。