Asynchronously consume synchronous WCF service

后端 未结 3 567
北荒
北荒 2020-12-08 02:58

I’m currently in the process of migrating a client application over to .NET 4.5 to make use of async/await. The application is a client for a WCF service which currently off

相关标签:
3条回答
  • 2020-12-08 03:51

    This isn't horrible: https://stackoverflow.com/a/23148549/177333. You just wrap your return values with Task.FromResult(). You have to change the service side, but it's still synchronous and you're not using an extra thread. That changes your server-side interface which can still be shared with the client so it can wait asynchronously. Otherwise it looks like you have to maintain separate contracts on server and client somehow.

    0 讨论(0)
  • 2020-12-08 03:59

    The client side and server side are totally separate from an async standpoint, they do not care about each other at all. You should have your sync function on your sever and only the sync function on your server.

    If you want to do it "right", on the client you will not be able to reuse the same interface for your generating your channel factory as the interface that is used to generate the server.

    So your server side would look like this

    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";
            }
        }
    }
    

    and your client side would look like this

    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();
        }
    }
    
    0 讨论(0)
  • 2020-12-08 04:00

    If your server-side API can be naturally async (like your Task.Delay example, rather than the Task.Run wrapper), declare it as Task-based in the contract interface. Otherwise, just leave it synchronous (but don't use Task.Run). Do not create multiple endpoints for the sync and async versions of the same method.

    The generated WSDL remains the same for async and sync contract APIs, I just found out that myself: Different forms of the WCF service contract interface. Your clients will keep running unchanged. By making your server-side WCF method asynchronous, all you do is improve the service scalability. Which is a great thing to do, of course, but wrapping a synchronous method with Task.Run would rather hurt the scalability than improve it.

    Now, the client of your WCF service doesn't know if the method is implemented as synchronous or asynchronous on the server, and it doesn't need to know that. The client can call your method synchronously (and block the client's thread) or it can call it asynchronously (without blocking the client's thread). In either case, it won't change the fact that the SOAP response message will be sent to the client only when the method has fully completed on the server.

    In your test project, you're trying to exposes different versions of the same API under different contract names:

    [ServiceContract]
    public interface IExampleService
    {
        [OperationContract(Name = "GetTest")]
        string GetTest();
    
        [OperationContract(Name = "GetTestAsync")]
        Task<string> GetTestAsync();
    
        [OperationContract(Name = "GetTestRealAsync")]
        Task<string> GetTestRealAsync();
    }
    

    This doesn't really make sense, unless you want to give your client an option to control if the method runs synchronously or asynchronously on the server. I cannot see why you would want this, but even if you have your reason, you'd be better off controlling this via a method argument and a single version of the API:

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

    Then, in the implementation your could do:

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

    @usr explains this in great details here. To sum up, it is not like the WCF service calls back your client to notify about the completion of the async operation. Rather, it simply sends back the full SOAP response using the underlying network protocol when it's done. If you need more than that, you could use WCF callbacks for any server-to-client notifications, but that would span the boundaries of a single SOAP message.

    0 讨论(0)
提交回复
热议问题