使用Task簡化Silverlight調用Wcf


從.Net4.0開始,.Net提供了一個Task類來封裝一個異步操作,用來簡化異步方法的調用。.Net4.5更進一步,添加了async和await兩個關鍵字,異步編程同步化,不用再寫一堆散亂的回調或者完成事件處理。Silverlight5開始支持Task類,但是要用await的話就需要編譯器的支持,VS2012直接支持,如果是VS2010,那就要安裝Async CTP,而Silverlight4沒有Task類,但安裝Async CTP后,得到的AsyncCtpLibrary_Silverlight.dll會帶有適合Silverlight4使用的Task類。下面介紹怎么使用。

使用Task封裝異步方法

可能大家都知道,Task不是一個靜態的工具類,我們不是調用它的一個靜態方法來簡化異步的調用,而是首先要將異步操作封裝到一個Task中,然后再使用這個封裝好的Task,一個Task就包含了開始和回調(或者是開始和完成事件處理),這樣對於Task的消費者來說,異步操作的復雜性就隱藏在Task的背后了。TaskFactory<TResult>類能幫助我們快速封裝異步方法到一個Task中。例如:

public Task<UserInfo> CreateUserTaskAsync(UserInfo user)
{
    return Task<UserInfo>.Factory.FromAsync(this.Channel.BeginCreateUser, this.Channel.EndCreateUser, user, null);
}

對於Wcf服務的方法,我暫時發現微軟提供了兩種方式自動生成這些基於Task方法。

1.VS2012+.Net4.5

VS2012下.Net4.5的項目在添加服務引用時有如下設置可以生成基於Task的方法

image

生成后的XXXAsync方法的返回類型就是Task<T>。例如:

public System.Threading.Tasks.Task<string> SayHelloToAsync(string name)
{
    return base.Channel.SayHelloToAsync(name);
}

2.Net4.0+TaskWsdlImportExtension.dll

如果使用VS2010或者在2012下的項目的目標框架是.Net4.0的話,是沒有上面那個選項的。這時可以借助安裝Async CTP后得到的TaskWsdlImportExtension.dll來幫助生成。首先對應的項目中引用TaskWsdlImportExtension.dll,然后在web.config中添加如下配置:

<system.serviceModel>
    <client>
        <metadata>
            <wsdlImporters>
                <extension type="TaskWsdlImportExtension.TaskAsyncWsdlImportExtension, TaskWsdlImportExtension" />
            </wsdlImporters>
        </metadata>
    </client>
</system.serviceModel>

更新服務引用后,同樣得到基於Task的異步方法。

很可惜的是這個dll不適合Silverlight使用,所以我參考TaskWsdlImportExtension的封裝方式(使用Task.Factory.FromAsync),自己做了個小工具來生成這些方法。

自定義工具生成Silverlight項目的基於Task的操作

這個工具本來想寫成T4模版的,但對T4不是很熟,最后寫成了一個exe。

大概原理如下,首先遍歷項目下的所有叫Reference.cs的文件,動態把它編譯為Assembly,再反射這個Assembly,找那些BeginXXX方法,把這些方法封裝為Task方法放到一個跟原Service同命名空間,同一一個Client的部分類中。

使用方式可能有點麻煩,但個人認為可以接受。

1.在有服務引用的Silverlight類庫中添加一個類

image

2.右鍵這個類,選擇打開方式,添加,選擇小工具的exe

image

然后確定,即可生成一堆XXXTaskAsync方法。

當然,上面是第一次使用,以后使用時,打開方式上存在這個程序,只需右擊打開方式,再在列表選擇這個程序兩步即可。

image

生成的代碼大概如下:

image

下面再看看怎么使用這些方法。

1.使用async和await

Silverlight項目使用vs2012或者VS2010安裝了Async CTP的話,就可以使用async和await。

1.vs2010的話首先在在Async CTP下找到AsyncCtpLibrary_Silverlight.dll或者AsyncCtpLibrary_Silverlight5.dll,並添加引用

vs2012就在Nuget里搜索await,引用下面的這個包

image

1.普通使用

await的使用方式可能大家都知道,就不詳細介紹,就是在方法里加async關鍵字,在Task方法前加await關鍵字

async private void OnMainPageLoaded(object sender, RoutedEventArgs e)
{
    var users = await userServiceClient.GetAllTaskAsync();
    dataGrid1.ItemsSource = users;
}
async private void OnMultiTaskClick(object sender, RoutedEventArgs e)
{
    var num = await systemServiceClient.GetNumTaskAsync(3);
    var result = await systemServiceClient.SayHiToTaskAsync(num.ToString("n2"));
    var time = await systemServiceClient.GetSerivceTimeTaskAsync();
    var users = await userServiceClient.GetAllTaskAsync();
    MessageBox.Show(string.Format("Num:{1},{0}{2},{0}Time:{3},{0}UserCount:{4}", Environment.NewLine,
                        num.ToString("n2"),
                        result,
                        time.ToString(),
                        users.Count.ToString()));
}

可以看到OnMultiTaskClick的第二個wcf方法是依賴於第一個wcf方法的結果的,這里感覺完全是”同步編程“,結果沒有問題:

image

2.Debug

使用await做Debug時跟同步編程一樣,無論單步或是其它,得心應手:

image

3.異常

調用wcf時產生的異常信息也跟不使用Task時一樣的,對於Wcf返回來的異常類型是FaultException<ExceptionDetail>。把null傳到下面這個方法產生的異常信息。

[OperationContract]
public string SayHiTo(string name)
{
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentNullException("name");

    return string.Format("Hi,{0}!", name);
}
image

2.后台線程+Task.Wait

如果沒有VS2012或者不想安裝Async CTP的話,就可以使用這種方式。由於Task.Wait是會阻塞線程的,在Silverlight的主線程是不允許這樣做的,所以把操作放到后台線程去做,既然是后台線程,所以就會存在跨線程操作UI的問題,要記得使用Dispatcher.BeginInvoke來操作UI。

1.普通使用

private void OnMainPageLoaded(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem((s) =>
    {
        var task = userServiceClient.GetAllTaskAsync();
        task.Wait();
        Dispatcher.BeginInvoke(() =>
        {
            dataGrid1.ItemsSource = task.Result;
        });
    });
}
private void OnMultiTaskClick(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem((s) =>
    {
        var task1 = systemServiceClient.GetNumTaskAsync(5);
        task1.Wait();
        var task2 = systemServiceClient.SayHiToTaskAsync(task1.Result.ToString());
        var task3 = systemServiceClient.GetSerivceTimeTaskAsync();
        var task4 = userServiceClient.GetAllTaskAsync();
        task2.Wait();
        task3.Wait();
        task4.Wait();
        Dispatcher.BeginInvoke(() =>
        {
            MessageBox.Show(string.Format("Num:{1},{0}{2},{0}Time:{3},{0}UserCount:{4}", Environment.NewLine,
                task1.Result.ToString(), 
                task2.Result, 
                task3.Result.ToString(), 
                task4.Result.Count.ToString()));
        });
    });
}

跟上面一樣,OnMultiTaskClick的第二個方法依賴於第一個方法,結果雷同。

image

這種方法感覺沒有上面的await那么好,但也比直接編寫的APM(Begin/End)或者EAP(完成事件)要方便不少。

2.Debug

使用這種方式Debug時要把斷點放到委托內,其它一樣,Wait后得到結果。

image

3.異常

這里調用wcf產生的異常有點不一樣,原本的異常變成了AggregateException,而真正的FaultException<ExceptionDetail>跑到了InnerException里去了

image

使用這個方式還有一點要注意,調用wcf的產生異常跳到Application_UnhandledException時也是后台線程,需要使用Deployment.Current.Dispatcher.BeginInvoke來操作UI,例如:

private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    var msgFormat = "Message:{0} {1} StackTrace:{2}";
    var msg = e.ExceptionObject.Message;
    var stackTrace = e.ExceptionObject.StackTrace;
    if (e.ExceptionObject.InnerException != null && e.ExceptionObject.InnerException is FaultException<ExceptionDetail>)
    {
        var ex = e.ExceptionObject.InnerException as FaultException<ExceptionDetail>;
        msg = ex.Detail.Message;
        stackTrace = ex.Detail.StackTrace;
    }
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        MessageBox.Show(string.Format(msgFormat, msg, System.Environment.NewLine, stackTrace));
    });
    e.Handled = true;
}

總結

1.對於生成Task方法的方式,如果是.Net項目可以選擇微軟提供的方法,VS2012+.Net4.5直接在服務引用生成,其它使用TaskWsdlImportExtension.dll。

2.對於Silverlight項目的話,只能自己制造工具去包裝了。

3.條件滿足的話盡量使用await的方式來使用Task(VS2012或者VS2010+Async CTP)

4.使用VS2010又不想安裝Async CTP就用后台線程+Task.Wait的方式,多注意后台線程不能調用UI的問題和異常處理的一點差異。

工具源碼+測試Task測試代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM