1. 程式人生 > >F#與ASP.NET(2):使用F#實現基於事件的非同步模式

F#與ASP.NET(2):使用F#實現基於事件的非同步模式

上一篇文章中,我們的簡單討論了.NET中兩種非同步模型以及它們在異常處理上的區別,並且簡單觀察了ASP.NET MVC 2中非同步Action的編寫方式。從中我們得知,ASP.NET MVC 2的非同步Action並非使用了傳統基於Begin/End的非同步程式設計模型,而是另一種基於事件的非同步模式。此外,ASP.NET MVC 2對於這種非同步模式提供了必要的支援,使此方面的程式設計變得相對簡單一些。但是,簡單的原因主要還是在於已經由其他元件提供了良好的,基於事件的非同步模式。那麼現在我們就來看看一般我們應該如何來實現這樣的功能,以及F#是如何美化我們的生活的吧。

非同步資料傳輸

我們為什麼要非同步,主要目的之一還是提高I/O操作的伸縮性。I/O操作主要是I/O裝置的事情,這些裝置在好好工作的時候,是不需要系統花心思進行照看的,它們只要能夠在完成指定工作後通知系統就可以了。這也是非同步I/O高效的原理,因為它將程式從等待I/O完成的苦悶中解脫出來,這對使用者或是系統來說都是一件絕好的事情。

那麼說到I/O操作,最典型的場景之一便是資料傳輸了。比如有兩個資料流streamIn和streamOut,我們需要非同步地從streamIn中讀取資料,並非同步地寫入到streamOut中。在這個過程中,我們使用一個相對較小的byte陣列作為快取空間,這樣程式在進行資料傳輸時便不會佔用太多記憶體。那麼,如果現在需要您編寫一個元件完成這樣的資料傳輸工作,並使用標準的基於事件的非同步模式釋放出來,您會怎麼做?

再囉嗦一次,基於事件的非同步模式,要求在任務完成時使用事件進行提示。同時在出錯的時候將異常物件儲存在事件的引數中。現在我已經幫您寫好了這樣的事件引數:

public class CompletedEventArgs 
: EventArgs { public CompletedEventArgs(Exception ex) { this.Error = ex; } public Exception Error { get; private set; } }

那麼接下來的工作就交給您了,加油!

嗯?那麼快就寫完啦?再想想?如果真確定了,就展開下面的程式碼對比一下吧:

展開程式碼
隱藏程式碼

public class AsyncTransfer
{
    private Stream m_streamIn;
    private 
Stream m_streamOut; public AsyncTransfer(Stream streamIn, Stream streamOut) { this.m_streamIn = streamIn; this.m_streamOut = streamOut; } public void StartAsync() { byte[] buffer = new byte[1024]; this.m_streamIn.BeginRead( buffer, 0, buffer.Length, this.EndReadInputStreamCallback, buffer); } private void EndReadInputStreamCallback(IAsyncResult ar) { var buffer = (byte[])ar.AsyncState; int lengthRead; try { lengthRead = this.m_streamIn.EndRead(ar); } catch (Exception ex) { this.OnCompleted(ex); return; } if (lengthRead <= 0) { this.OnCompleted(null); } else { try { this.m_streamOut.BeginWrite( buffer, 0, lengthRead, this.EndWriteOutputStreamCallback, buffer); } catch (Exception ex) { this.OnCompleted(ex); } } } private void EndWriteOutputStreamCallback(IAsyncResult ar) { try { this.m_streamOut.EndWrite(ar); var buffer = (byte[])ar.AsyncState; this.m_streamIn.BeginRead( buffer, 0, buffer.Length, this.EndReadInputStreamCallback, buffer); } catch (Exception ex) { this.OnCompleted(ex); } } private void OnCompleted(Exception ex) { var handler = this.Completed; if (handler != null) { handler(this, new CompletedEventArgs(ex)); } } public event EventHandler<CompletedEventArgs> Completed; }

是不是很複雜的樣子?

編寫非同步程式,基本則意味著要將原本同步的呼叫拆成兩段:發起及回撥,這樣便讓上下文狀態的儲存便的困難起來。幸運的是,C#這門語言提供了方便好用的匿名函式語法,這對於編寫一個回撥函式來說已經非常容易了。但是,如果需要真正寫一個穩定、安全的非同步程式,需要做的事情還有很多。例如,一次非同步操作結束之後會執行一個回撥函式,那麼如果在這個回撥函式中丟擲了一個異常那該怎麼辦?如果不正確處理這個異常,輕則造成資源洩露,重則造成程序退出。因此在每個回撥函式中,您會發現try...catch塊是必不可少的——甚至還需要兩段。

更復雜的可能還是在於邏輯控制上。這樣一個數據傳輸操作很顯然需要迴圈——讀一段,寫一段。但是由於需要編寫成二段式的非同步呼叫,因此程式的邏輯會被拆得七零八落,我們沒法使用一個while塊包圍整段邏輯。

編寫一個非同步程式本來就是那麼複雜。

編寫簡單的代理

嗯,我們繼續。現在我們已經有了一個非同步傳輸資料的元件,就用它來做一些有趣的事情吧。例如,我們可以在ASP.NET應用程式中建立一個簡單的代理,即給定一個URL,在伺服器端發起這樣一個請求,並將這個URL的資料傳輸到客戶端來。簡單起見,除了進行資料傳輸之外,我們只需要簡單地輸出Content Type頭資訊即可。

寫好了嗎?我也寫了一個,僅供參考:

展開程式碼
隱藏程式碼

public class AsyncWebTransfer
{
    private WebRequest m_request;
    private WebResponse m_response;

    private HttpContextBase m_context;
    private string m_url;

    public AsyncWebTransfer(HttpContextBase context, string url)
    {
        this.m_context = context;
        this.m_url = url;
    }

    public void StartAsync()
    {
        this.m_request = WebRequest.Create(this.m_url);
        this.m_request.BeginGetResponse(this.EndGetResponseCallback, null);
    }

    private void EndGetResponseCallback(IAsyncResult ar)
    {
        try
        {
            this.m_response = this.m_request.EndGetResponse(ar);
            this.m_context.Response.ContentType = this.m_response.ContentType;

            var streamIn = this.m_response.GetResponseStream();
            var streamOut = this.m_context.Response.OutputStream;

            var transfer = new AsyncTransfer(streamIn, streamOut);
            transfer.Completed += (sender, args) => this.OnCompleted(args.Error);
            transfer.StartAsync();
        }
        catch(Exception ex)
        {
            this.OnCompleted(ex);
        }
    }

    private void OnCompleted(Exception ex)
    {
        if (this.m_response != null)
        {
            this.m_response.Close();
            this.m_response = null;
        }

        var handler = this.Completed;
        if (handler != null)
        {
            handler(this, new CompletedEventArgs(ex));
        }
    }

    public event EventHandler<CompletedEventArgs> Completed;
}

如果說之前的AsyncTransfer類是基於“Begin/End非同步程式設計模型”實現的基於事件的非同步模式,那麼AsyncWebTransfer便是基於“基於事件的非同步模式”實現的基於事件的非同步模式了。嗯,似乎有點繞口,不過我相信這段程式碼對您來說還是不難理解的。

使用F#完成非同步工作

使用F#實現基於事件的非同步模式

總結

這便是F#的偉大之處。時常有朋友會問我為什麼對F#有那麼大的興趣,我想,如果藉助F#可以用十分之一的時間,十分之一的程式碼行數,寫出執行效果相同,但可維護性高出好幾倍的程式來——我為什麼會不感興趣呢?

您可以在這裡訪問到本文的示例程式碼,其中我在WebApp專案中實現了簡單的入口頁面,您訪問“/image”便會出現兩個文字框,您填入一個URL(例如某幅圖片)並提交,它會將URL提交至“/image/load”或“/image/loadfs”中,它們分別使用了C#和F#實現的AsyncWebTransfer類,從效果上說兩者完全相同。

相關文章