2008-10-29 18 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(); 
    } 
}