2008-10-29 21 views
2

一些API,如WebClient,使用Event-based Async pattern。虽然这看起来很简单,并且可能在松散耦合的应用程序(例如,UI中的BackgroundWorker)中运行良好,但它并没有很好地链接在一起。C#中基于事件的异步;任何泛型重构可能?

例如,这是一个多线程程序,所以异步工作不会阻止。 (想象一下,这是在一个服务器应用程序中进行的,并且调用了数百次 - 你不想阻塞你的ThreadPool线程。)我们得到3个局部变量(“状态”),然后进行2次异步调用,结果是首先进入第二个请求(所以它们不能平行)。状态也可能发生变化(易于添加)。

使用Web客户端,事情最终会像下面(或者你最终创造了一堆的对象要像闭包):

using System; 
using System.Net; 

class Program 
{ 
    static void onEx(Exception ex) { 
     Console.WriteLine(ex.ToString()); 
    } 

    static void Main() { 
     var url1 = new Uri(Console.ReadLine()); 
     var url2 = new Uri(Console.ReadLine()); 
     var someData = Console.ReadLine(); 

     var webThingy = new WebClient(); 
     DownloadDataCompletedEventHandler first = null; 
     webThingy.DownloadDataCompleted += first = (o, res1) => { 
      if (res1.Error != null) { 
       onEx(res1.Error); 
       return; 
      } 
      webThingy.DownloadDataCompleted -= first; 
      webThingy.DownloadDataCompleted += (o2, res2) => { 
       if (res2.Error != null) { 
        onEx(res2.Error); 
        return; 
       } 
       try { 
        Console.WriteLine(someData + res2.Result); 
       } catch (Exception ex) { onEx(ex); } 
      }; 
      try { 
       webThingy.DownloadDataAsync(new Uri(url2.ToString() + "?data=" + res1.Result)); 
      } catch (Exception ex) { onEx(ex); } 
     }; 
     try { 
      webThingy.DownloadDataAsync(url1); 
     } catch (Exception ex) { onEx(ex); } 

     Console.WriteLine("Keeping process alive"); 
     Console.ReadLine(); 
    } 

}

有没有重构这一事件的通用方式基于异步模式? (也就是说,不必为每个这样的API编写详细的扩展方法?)BeginXXX和EndXXX使它变得简单,但这种事件方式似乎没有提供任何方式。

回答

1

你可能想看看F#。通过其“工作流程”功能,F#可以自动完成此编码。 F#的'08 PDC演示文稿使用称为async的标准库工作流程处理异步Web请求,该工作流程处理BeginXXX/EndXXX模式,但您可以毫不费力地为事件模式编写工作流程,或者找到一个固定模式的工作流程。而F#可以很好地与C#配合使用。

+0

对安东的答案进行详细说明。术语“异步工作流程”是您搜索的内容,请参阅http://blogs.msdn.com/dsyme/archive/2007/10/11/introducing-f-asynchronous-workflows.aspx – 2009-03-13 17:13:35

4

在过去,我已经使用迭代器方法实现了这一点:每次你想要另一个URL请求时,你使用“yield return”来将控制权交还给主程序。一旦请求结束,主程序就会回调你的迭代器来执行下一项工作。

您正在有效地使用C#编译器为您编写状态机。好处是你可以在迭代器方法中编写看起来很正常的C#代码来驱动整个事情。

using System; 
using System.Collections.Generic; 
using System.Net; 

class Program 
{ 
    static void onEx(Exception ex) { 
     Console.WriteLine(ex.ToString()); 
    } 

    static IEnumerable<Uri> Downloader(Func<DownloadDataCompletedEventArgs> getLastResult) { 
     Uri url1 = new Uri(Console.ReadLine()); 
     Uri url2 = new Uri(Console.ReadLine()); 
     string someData = Console.ReadLine(); 
     yield return url1; 

     DownloadDataCompletedEventArgs res1 = getLastResult(); 
     yield return new Uri(url2.ToString() + "?data=" + res1.Result); 

     DownloadDataCompletedEventArgs res2 = getLastResult(); 
     Console.WriteLine(someData + res2.Result); 
    } 

    static void StartNextRequest(WebClient webThingy, IEnumerator<Uri> enumerator) { 
     if (enumerator.MoveNext()) { 
      Uri uri = enumerator.Current; 

      try { 
       Console.WriteLine("Requesting {0}", uri); 
       webThingy.DownloadDataAsync(uri); 
      } catch (Exception ex) { onEx(ex); } 
     } 
     else 
      Console.WriteLine("Finished"); 
    } 

    static void Main() { 
     DownloadDataCompletedEventArgs lastResult = null; 
     Func<DownloadDataCompletedEventArgs> getLastResult = delegate { return lastResult; }; 
     IEnumerable<Uri> enumerable = Downloader(getLastResult); 
     using (IEnumerator<Uri> enumerator = enumerable.GetEnumerator()) 
     { 
      WebClient webThingy = new WebClient(); 
      webThingy.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e) { 
       if (e.Error == null) { 
        lastResult = e; 
        StartNextRequest(webThingy, enumerator); 
       } 
       else 
        onEx(e.Error); 
      }; 

      StartNextRequest(webThingy, enumerator); 
     } 

     Console.WriteLine("Keeping process alive"); 
     Console.ReadLine(); 
    } 
}