Michael
Michael

Reputation: 1010

Async Task.Run in a self hosted web service - is it "wrong usage" as long as it works?

I am working on a project with the following details:

Here is a most simple example of one of the many webservice methods:

public async Task<int> Count()
{
        int result = 0;

        //There is nothing to await here, so I use Task.Run
        await Task.Run(() =>
        {
            using (IDB ctx = new DB())
            {
                result = ctx.Customers.All.Count();
            }
            //Here could happen a lot more
           //System.Threading.Thread.Sleep(10000); 

        }).ConfigureAwait(false);

        return result;
}

As you can see, I am using Task.Run to access some data, because non of the repository interfaces offers async methods. I can not await anything. If I wanted to do "real async", I would have to rewrite the complete repository interface.

If I would not use Task.Run, the server would be blocking all other incoming requests.

My 2 questions are:

  1. Is there anything wrong using Task.Run in this scenario?

  2. Even if it is working and maybe not completely wrong, is there a better, more professional solution to call synchronous code in an async method?

The initial reason for this question is, that I read, that using Task.Run in an async method is "fake async". (I think Task.Run starts a new thread, while "real async" code does not)

I answered my own question, see answer below. I hope it can help others.

Upvotes: 0

Views: 1184

Answers (2)

Michael
Michael

Reputation: 1010

First of all: Thank all of you for your hints. I needed them to dive deeper into the problem.

I have found the real solution to this problem and I think, I could add some value to the community by answering my own question in detail.

The solution can also be found in this great article: https://www.oreilly.com/library/view/learning-wcf/9780596101626/ch04s04.html

Here is a quick summary of the initial problem and the solution:

The goal

  • My goal is to host multiple self-hosted WCF services in a .NET 4.5 application
  • All self-hosted WCF services are accessible to multiple clients
  • All self-hosted WCF services MUST NOT block each other when multiple users are using them

The problem (and the false solution) (my initial question)

  • My problem was, that whenever one client used a webservice, it would block the other webservices until it returned to the client
  • It did not matter what kind of InstanceContextMode or ConcurrencyMode I used
  • My false solution was to use async and Task.Run ("Fake Async"). It worked, but it was not the real solution.

The solution (found in the article)

  • When self-hosting WCF webservices, you MUST make sure, that you always call ServiceHost.Open in a seperate thread, different from the UI thread
  • Whenever you open a ServiceHost in a Console, WinForms or WPF application, or a Windows Service, you have to be aware, at which time you call ServiceHost.Open and how you use the ServiceBehaviorAttribute.UseSynchronizationContext
  • The default value for ServiceBehaviorAttribute.UseSynchronizationContext is True. (this is bad and leads to blocking!)
  • If you just call ServiceHost.Open, without setting UseSynchronizationContext = false , all ServiceHosts will run in the UI thread and block this thread and each other.

Solution 1 (tested and it works - no more blocking)

  • Set ServiceBehaviorAttribute.UseSynchronizationContext = false

Solution 2 (tested and it works - no more blocking)

  • Do NOT touch ServiceBehaviorAttribute.UseSynchronizationContext, just let it be true
  • But create at least one or multiple threads in which you call ServiceHost.Open

Code:

private List<ServiceHost> _ServiceHosts = new List<ServiceHost>();
private List<Thread> _Threads = new List<Thread>(); 

foreach (ServiceHost host in _ServiceHosts)
{
   _Threads.Add(new Thread(() => { host.Open(); }));
   _Threads[_Threads.Count - 1].IsBackground = true;
   _Threads[_Threads.Count - 1].Start();
}

Solution 3 (not tested, but mentioned in the article)

  • Do NOT touch ServiceBehaviorAttribute.UseSynchronizationContext, just let it be true
  • But make sure, that you call ServiceHost.Open BEFORE the UI thread is created
  • Then the ServiceHosts will use a different thread and not block the UI thread

I hope this can help others with the same problem.

Upvotes: 1

TheGeneral
TheGeneral

Reputation: 81493

Yes it is fake async and less scalable as it starts a thread and blocks it, there is no giving it back until its finished.

However,

I call such methods “fake-asynchronous methods” because they look asynchronous but are really just faking it by doing synchronous work on a background thread. In general, do not use Task.Run in the implementation of the method; instead, use Task.Run to call the method. There are two reasons for this guideline:

  • Consumers of your code assume that if a method has an asynchronous signature, then it will act truly asynchronously. Faking asynchronicity by just doing synchronous work on a background thread is surprising behavior.
  • If your code is ever used on ASP.NET, a fake-asynchronous method leads developers down the wrong path. The goal of async on the server side is scalability, and fake-asynchronous methods are less scalable than just using synchronous methods.

The idea of exposing “async over sync” wrappers is also a very slippery slope, which taken to the extreme could result in every single method being exposed in both synchronous and asynchronous forms. Many of the folks that ask me about this practice are considering exposing async wrappers for long-running CPU-bound operations. The intention is a good one: help with responsiveness. But as called out, responsiveness can easily be achieved by the consumer of the API, and the consumer can actually do so at the right level of chunkiness, rather than for each chatty individual operation. Further, defining what operations could be long-running is surprisingly difficult. The time complexity of many methods often varies significantly.

However, you actually really dont fit into either of these categories. From your description you are hosting this WCF service. It will run your code asynchronously anyway if you have set the InstanceContextMode and ConcurrencyMode correctly. Your will additionally have the ability to run the TBA wrappers for your call form the client, assuming you generated your proxies with the appropriate settings.

If i understand you correctly, you could just let this method be entirely synchronous, and let WCF take care of the details and save resources

Update

An example: If I use Task.Run inside any webservice methode, I can even call Thread.Sleep(10000) inside Task.Run and the server stays responsive to any incoming traffic.

I think the following might help you the most

Sessions, Instancing, and Concurrency

A session is a correlation of all messages sent between two endpoints. Instancing refers to controlling the lifetime of user-defined service objects and their related InstanceContext objects. Concurrency is the term given to the control of the number of threads executing in an InstanceContext at the same time.

Its seems like your WCF service is setup for InstanceContextMode.PerSession, and ConcurrencyMode.Single. If your service is stateless you probably want to use InstanceContextMode.PerCall and only use async when you have something that truly can be awaited

Upvotes: 1

Related Questions