簡體   English   中英

異步使用同步WCF服務

[英]Asynchronously consume synchronous WCF service

我目前正在將客戶端應用程序遷移到.NET 4.5以使用async / await。 該應用程序是WCF服務的客戶端,該服務目前僅提供同步服務。 我現在想知道, 我應該如何異步使用這種同步服務

我使用渠道工廠連接到WCF服務,利用服務器和客戶端共享的服務合同。 因此,我無法使用VisualStudio或svcutil的自動生成來生成異步客戶端代理。

我已經閱讀了這個相關的問題該問題是關於是否使用Task.Run在客戶端包裝同步調用,或者是否使用異步方法擴展服務契約。 答案表明,服務器提供的“真正”異步方法對於客戶端性能更好,因為沒有線程必須主動等待服務調用完成。 這對我來說很有意義,這意味着同步調用應該包含在服務器端。

另一方面,Stephen Toub在這篇博客文章中一般都不贊成這樣做。 現在,他沒有在那里提到WCF,所以我不確定這是否適用於在同一台機器上運行的庫,或者它是否也適用於遠程運行的東西,但是異步性的引入對它的實際影響是連接/傳輸。

畢竟,由於服務器實際上並不是異步工作(並且可能不會在另一個時間內工作),一些線程將始終必須等待:在客戶端或服務器上。 這同樣適用於同步使用服務(當前,客戶端在后台線程上等待以保持UI響應)。

為了使問題更清楚,我准備了一個例子。 完整項目可在此處下載

服務器提供同步服務GetTest 這是當前存在的,並且工作同步發生的地方。 一種選擇是將其包裝在異步方法中,例如使用Task.Run ,並將該方法作為合同中的附加服務提供(需要擴展合同接口)。

// currently available, synchronous service
public string GetTest() {
    Thread.Sleep(2000);
    return "foo";
}

// possible asynchronous wrapper around existing service
public Task<string> GetTestAsync() {
    return Task.Run<string>(() => this.GetTest());
}

// ideal asynchronous service; not applicable as work is done synchronously
public async Task<string> GetTestRealAsync() {
    await Task.Delay(2000);
    return "foo";
}

現在,在客戶端,此服務是使用渠道工廠創建的。 這意味着我只能訪問服務契約定義的方法,除非我明確定義和實現它們,否則我特別無法訪問異步服務方法。

根據現有的方法,我有兩種選擇:

  1. 我可以通過包裝調用來異步調用同步服務:

     await Task.Run<string>(() => svc.GetTest()); 
  2. 我可以直接異步調用異步服務,這是由服務器提供的:

     await svc.GetTestAsync(); 

兩者都很好,不會阻止客戶端。 這兩種方法都涉及在某一端忙於等待:選項1在客戶端上等待,這相當於之前在后台線程中所做的事情。 選項2通過在那里包裝同步方法在服務器上等待。

什么是使異步感知同步WCF服務的推薦方法? 我應該在客戶端或服務器上執行打包的位置? 或者有沒有更好的選擇,無需等待任何地方,即通過在連接上引入“真正的”異步性 - 就像生成的代理一樣?

客戶端和服務器端完全獨立於異步立場,他們根本不關心彼此。 您應該在服務器上具有同步功能,並且只有服務器上的同步功能。

如果要“正確”執行此操作,則在客戶端上,您將無法重復使用相同的接口來生成通道工廠,作為用於生成服務器的接口。

所以你的服務器端看起來像這樣

using System.ServiceModel;
using System.Threading;

namespace WcfService
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        string GetTest();
    }

    public class Service1 : IService
    {
        public string GetTest()
        {
            Thread.Sleep(2000);
            return "foo";
        }
    }
}

而你的客戶端看起來像這樣

using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SandboxForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            var button = new Button();
            this.Controls.Add(button);

            button.Click += button_Click;
        }

        private async void button_Click(object sender, EventArgs e)
        {
            var factory = new ChannelFactory<IService>("SandboxForm.IService"); //Configured in app.config
            IService proxy = factory.CreateChannel();

            string result = await proxy.GetTestAsync();

            MessageBox.Show(result);
        }
    }

    [ServiceContract]
    public interface IService
    {
        [OperationContract(Action = "http://tempuri.org/IService/GetTest", ReplyAction = "http://tempuri.org/IService/GetTestResponse")]
        Task<string> GetTestAsync();
    }
}

如果您的服務器端API可以是自然異步的(就像您的Task.Delay示例,而不是Task.Run包裝器),請在合同接口中將其聲明為基於Task 否則,只需保持同步(但不要使用Task.Run )。 不要為同一方法的同步和異步版本創建多個端點。

生成的WSDL對於異步和同步合同API保持不變,我只是發現了自己: WCF服務合同接口的不同形式 您的客戶將繼續保持運營。 通過使服務器端WCF方法異步,您所做的只是提高服務可伸縮性。 當然,這是一件很棒的事情,但是使用Task.Run包裝同步方法會損害可擴展性而不是改進它。

現在,您的WCF服務的客戶端不知道該方法是在服務器上實現為同步還是異步,並且它不需要知道。 客戶端可以同步調用您的方法(並阻止客戶端的線程),也可以異步調用它(不阻塞客戶端的線程)。 在任何一種情況下, 只有當方法在服務器上完全完成時,才會改變SOAP響應消息將被發送到客戶端的事實。

您的測試項目中 ,您嘗試在不同的合同名稱下公開相同API的不同版本:

[ServiceContract]
public interface IExampleService
{
    [OperationContract(Name = "GetTest")]
    string GetTest();

    [OperationContract(Name = "GetTestAsync")]
    Task<string> GetTestAsync();

    [OperationContract(Name = "GetTestRealAsync")]
    Task<string> GetTestRealAsync();
}

除非您希望為客戶端提供控制方法是否在服務器上同步或異步運行的選項,否則這並沒有多大意義。 我不明白為什么你會想要這個 ,但即使你有理由,你最好通過方法參數和API的單一版本來控制它:

[ServiceContract]
public interface IExampleService
{
    [OperationContract]
    Task<string> GetTestAsync(bool runSynchronously);
}

然后,在實施中你可以這樣做:

Task<string> GetTestAsync(bool runSynchronously)
{
    if (runSynchronously)
        return GetTest(); // or return GetTestAsyncImpl().Result;
    else
        return await GetTestAsyncImpl();
}

@usr在這里詳細解釋了這一點 綜上所述, 不像 WCF服務回調客戶端,通知有關異步操作的完成 相反,它只是在完成后使用底層網絡協議發回完整的SOAP響應。 如果您需要更多,則可以對任何服務器到客戶端通知使用WCF回調 ,但這將跨越單個SOAP消息的邊界。

這並不可怕: https//stackoverflow.com/a/23148549/177333 您只需使用Task.FromResult()包裝返回值。 您必須更改服務端,但它仍然是同步的,並且您沒有使用額外的線程。 這會改變您的服務器端接口,該接口仍然可以與客戶端共享,因此它可以異步等待。 否則看起來你必須以某種方式在服務器和客戶端上維護單獨的合同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM