Reputation: 1010
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:
Is there anything wrong using Task.Run in this scenario?
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
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
The problem (and the false solution) (my initial question)
The solution (found in the article)
Solution 1 (tested and it works - no more blocking)
Solution 2 (tested and it works - no more blocking)
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)
I hope this can help others with the same problem.
Upvotes: 1
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