0

I have a complex method, this method make webRequest, access database and other jobs.

public string MyComplexMethod(params){
    //Access db
    //Make WebRequests
    //...
}

I made this method asynchronous this way:

public async Task<string> MyComplexMethod(params)
{
    //Access db
    //Make WebRequests
    //...
}

public async Task MyComplexMethodAsync(params)
{
    await Task.Run(() => MyComplexMethod()).ConfigureAwait(false);
}

And I call my complex method this way:

public void OtherMethod()
{
    //other stuff
    MyComplexMethodAsync(params);
    //other stuff
}

Can I use async and mainly Task.Run with such complex method? Or there another way to make this method asynchronous?

  • There is no reason to ask another thread for your function - you're already on thread pool, you simply ask for one more thread, and pausing the first one. – VMAtm Apr 12 '17 at 14:45

2 Answers2

2

To be wrapped by Task.Run method don't need to be asynchronous

public string MyComplexMethod() {}

public Task MyComplexMethodAsync() 
{
    return Task.Run(() => MyComplexMethod());
}

But using sample above don't make your method to be asynchronous in the way async-await works.
You mentioned that your complex method uses webservices and database queries - this makes your method to be perfect candidate for making it asynchronous.
async-await was designed mostly for working with external resources effectively without using extra threads for operations which only waiting for response and do nothing.

Only you need, it creates own asynchronous method for every operation which "touches" external resources, most of the "clients" already provide asynchronous methods for working with databases or web services.

// Access Db
public async Task<object> GetValueFromDatabaseAsync()
{
    using (var connection = new SqlConnection(connectionString))
    using (var command = new SqlCommand(query, connection))
    {
        await connection.OpenAsync();
        return await command.ExecuteScalarAsync();
    }
}

// Make web request
public async Task<HttpResponseMessage> SendRequestToWebServiceAsync(SomeData data)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(webserviceUrl);
        return await client.PostAsJsonAsync("api/values", data);
    }
} 

Then you end up with your asynchronous complex method

public async Task<string> MyComplexMethodAsync() 
{
    var value = await GetValueFromDatabaseAsync();
    var data = new SomeData(value);
    var response = await SendRequestToWebServiceAsync(data);

    return response.StatusCode.ToString();
}

Interesting part of asynchronous approach, that after you starting using async-await methods in your application they starts spreading over whole application like a zombies :)

For using MyComplexMethodAsync yuo need change OtherMethod to be asynchronous too

public async Task OtherMethodAsync()
{
    //other stuff
    await MyComplexMethodAsync(params);
    //other stuff
}
Fabio
  • 31,528
  • 4
  • 33
  • 72
  • Thank you so much, your answer opened my mind about async/await I will change my code. But only to clear my doubts, is it "right" to put that complex method inside Task.Run? Or must I use Task.Run only with more simple methods? – Bruno Canto Apr 12 '17 at 03:21
  • 2
    @BrunoCanto, No - putting asynchronous methods inside `Task.Run` is wrong approach. Check this for why: [Don't Use Task.Run in the Implementation](https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html) – Fabio Apr 12 '17 at 04:28
1

I think you're on the right path with async / await.

async tells the compiler that you are going to wrap everything in that method ContinueWith AFTER you reach the await keyword.

public void Something()
{
     var task = new Task(() => DoWork());
     task.ContinueWith(() => MoreWorkAfter());
     task.Start();
}

//Is the same as

public async void Something()
{
     var task = new Task(() => DoWork());
     task.Start();
     await Task;
     MoreWorkAfter();
}

//Is also the same as 

public async void Something()
{
     var task = Task.Run(() => DoWork());
     await Task;
     MoreWorkAfter();
}

//Still the same as 

public async void Something()
{
     await Task.Run(() => DoWork());
     MoreWorkAfter();
}

To make the method itself awaitable it has to return a Task object also because Task has the GetAwaiter() the await is looking for. Just remember that if you don't START the task it will await forever. So here's the same method written in an awaitable way for others.

public Task SomethingAsync()
{
     return Task.Run(() => DoWork());
}

//Same as...

public async Task SomethingAsync()
{
     await Task.Run(() => DoWork());
}

//And now in other methods you can....

public async void AnotherMethod()
{
     await SomethingAsync();
     //Do more work after it's complete.
}

Something to take away is that the Task is running on a new thread once it's started and to answer your question, yes it offloads the work. So for your original method keep it the same. The async method can Task.Run it like you've done.

public string MyComplexMethod(params)
{
        //Access db
        //Make WebRequests
        //...
}

public async Task MyComplexMethodAsync(params)
{
    await Task.Run(() => MyComplexMethod()).ConfigureAwait(false);
}

Something to note however; is that although you may call:

await MyComplexMethodAsync(params);

You have ConfigureAwait(false);

Which means any code after the task, in that method, will not be on the current context.

public async Task MyComplexMethodAsync(params)
{
    //Main thread work here
    await Task.Run(() => MyComplexMethod()).ConfigureAwait(false);//Background work here
    //This will also be background work since CongfigureAwait(false);
}

public async Task MyComplexMethodAsync(params)
{
    //Main thread work here
    await Task.Run(() => MyComplexMethod()); //Background work here
    //Main thread work here again since CongfigureAwait defaults to true;
}

And I say Main thread work but that assumes the Main thread called MyComplexMethodAsync.

The easiest way to grasp it is just imagine everything AFTER the await keyword is basically a new Action that gets called when the task is complete; just like using ContinueWith.

AND if there is code AFTER the await keyword in your method, it will BE SCHEDULED to run on the Main thread (considering the Main thread called the method to start with). Meaning it's a little like using Dispatcher.BeginInvoke if you've ever use it. The remaining portion get's queued to run on the Main thread in better terms. (Again, unless you put ConfigureAwait(false) and then it doesn't)

Michael Puckett II
  • 6,586
  • 5
  • 26
  • 46
  • Why use the `Task.Run` inside the ASP.NET? You're already in thread pool, why ask for some more thread? And do not create tasks by constructor. – VMAtm Apr 12 '17 at 14:45
  • @VMAtm It doesn't say he's using ASP.NET for starters; unless I missed it. And Task.Run creates the task by constructor in the background for you. There are MANY times when you want to create a task by the constructor, however; for the sake of showing how it works I have provided implementations so that it comes full circle and hopefully makes sense. – Michael Puckett II Apr 12 '17 at 16:37
  • @VMAtm Just for the sake of saying it, why do you propose to never make Task via constructor? – Michael Puckett II Apr 12 '17 at 16:39
  • question is tagged as [tag:asp-net]. @StephenCleary had a [great article](http://blog.stephencleary.com/2014/05/a-tour-of-task-part-1-constructors.html). First line: TL;DR: Do not use `Task` or `Task` constructors. – VMAtm Apr 12 '17 at 16:46
  • @VMAtm I've read most of Stephen Cleary's articles. He has a fixed opinion of Tasks. There is one thing for certain; understand task and how async / await works is what you want to do. First off, Stephen never said not to use a Task in ASP.NET application. He said it doesn't make sense to await a task because there's no UI we don't need to offload the work. This is true. However; the above question isn't about awaiting a Task but rather running other work while the thread continues. An absolutely perfect reason to use a Task. – Michael Puckett II Apr 12 '17 at 16:56
  • [Another article](https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html) clearly states: This will *work* correctly, but it’s not at all *efficient* [to use the `Task.Run` in ASP.NET]. If we need the result of the long-running method, just call it (a)synchronously, don't introduce the context switch. If we don't need the results, use [other ways](https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx) to fire-and-forget inside ASP.NET – VMAtm Apr 12 '17 at 17:04
  • 1
    @VMAtm Sigh... I believe you mean well but you're naïve so I'll not argue the fact any longer. Creating multiple threads in an ASP.NET application is NOT good, I agree but there are times when it does become very practical and if you feel otherwise that's ok. As far as using Task constructors there are options in the constructor we don't get using Task.Run and if you're savvy about any of it then you can get more out of your work by doing the right thing at the right time. However; none of this matters. My entire answer was to help him understand how the task works for his own sake. – Michael Puckett II Apr 12 '17 at 17:08
  • Your answer is great, but **not for ASP.NET** app. OP clearly says that his methods are: `Access db`, `Make WebRequests`. These are `I/O` methods, which should be awaited, but shouldn't be wrapped into a `Task`. – VMAtm Apr 12 '17 at 17:10
  • And side note: `Task.Run` is not the same as `new Task`, and only last one is not recommended, as it should be used only by task schedulers. – VMAtm Apr 12 '17 at 17:28
  • @VMAtm Maybe you should read what I wrote and slow down... you might learn something yourself. I never said a new Task is the same as Task.Run I said both methods are the same. One I declare a task and then start it the other I use Task.Run. .. Task.Run will declare the task and start it for you... – Michael Puckett II Apr 12 '17 at 17:55
  • Yep, I read that, and still this is not a recommended way to start a task. You've answer the question about UI-application, not the web application, that's why I've downvoted - it's a useful answer for a different question. – VMAtm Apr 12 '17 at 18:23
  • @VMAtm Some people... Enjoy. – Michael Puckett II Apr 12 '17 at 18:26
  • @VMAtm if you want to set the TaskCreationOptions, how do you do that with a Task.Run? If you want to pass state to a Task, how do you do that with Task.Run? What about when you want to manage your own TaskFactory? Task.Run is a quick and simple way to start a task; it's not the only way by far. – Michael Puckett II Apr 12 '17 at 18:31
  • @VMAtm I think what you should consider is what a Task really is, how it's managed, and finally how async / await compiles the language to help you manage it. You seem to be only filled with the knowledge of articles and lack the wisdom of how it all really works down to the root. – Michael Puckett II Apr 12 '17 at 18:34
  • If you need to specify that (most cases I've seen it's when you need to provide some flags from `TaskCreationOptions`), you should use [`StartNew`](https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/) method for this. However, that method [considered dangerous](https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html) as is use `TaskScheduler.Current` rather than `TaskScheduler.Default`, so you should use the proper overload. If you really need to manage your own `TaskFactory`, this is the only recommended case to use the `Task` constructor. – VMAtm Apr 12 '17 at 18:37
  • By the way, if you really do manage your own `TaskFactory`, my honest congrats - in my career it was a rare case to really need that. – VMAtm Apr 12 '17 at 18:38
  • Peace guys! I'm using ASP.Net, and I don't have experience with Tasks and to avoid performance problems seems that Task.Run is not a good practice in this situation, because each user request will make a new Task.Run. I'm thinking in to put these proccesses in a queue and use a Task Scheduler or something like that to execute the processes generate by the users. Please correct me if i'm wrong. – Bruno Canto Apr 12 '17 at 18:39
  • @MichaelPuckettII I use the links for articles because they are easier to find rather than MSDN articles with detailed explanation about the `async/await`, which I've read too (as the books about `TPL` itself, say `Professional Parallel Programming with C#: Master Parallel Extensions With .NET 4` from Gastón C. Hillar. I don't see the contradictions from Cleary's articles. If you do, you can link other articles which are useful. You didn't link any of them for now. – VMAtm Apr 12 '17 at 18:41
  • @BrunoCanto as far as I can see, we have no war here, only a discussion about tasks. – VMAtm Apr 12 '17 at 18:43
  • @BrunoCanto Correct, there is no war. I am a little moved by the stiffness of the conversation but it's fine; to each their own. Here's the thing.. I've read Stephen Cleary's articles and I really really like the guy. He's a good guy, good heart, and I'd bat for em anytime; but I do not agree completely with his understand of tasks all the way down to the frame. I believe the biggest boundary that he hasn't discussed relates to multi-core processors as in any care, all code is asynchronous is his final answer. This really isn't true in a multi-core operation, give or take. – Michael Puckett II Apr 12 '17 at 19:06
  • @VMAtm the comment above was intended for us all but I wanted to pan something out. Instead of taking anyone's word for it; write out the application and prove the data yourself from 1 to thousands of request; even as a console app; for better understanding. Multithreaded programming is an art and to do it right is a masterpiece; even Task's don't make the art right but it's quicker to use an airbrush as opposed to a normal brush; and that's basically that it does. Cleans the code and makes it easier to maintain / debug. – Michael Puckett II Apr 12 '17 at 19:09
  • @MichaelPuckettII I'm sorry, but you still didn't provide any proves for your words about your position. Moreover, multithreading `!=` asynchronous, as `async` methods can easily be scheduled in one thread all the time. Multithreading means that we have more than 1 thread running simultaneously. I still cannot see any reasons to start a new `Task` (thread, whatever) in `ASP.NET` applications, as it *could* lead to context switch which is undesirable, even with multi-core. – VMAtm Apr 12 '17 at 19:23
  • @MichaelPuckettII Overhead from a state machine made by `async/await` **in this case** isn't that much hit for performance, comparing with `Task` usage, as it doesn't consume the additional thread from thread pool, if `async` method is properly written. – VMAtm Apr 12 '17 at 19:25
  • @VMAtm The await keyword ONLY works with Task. All it does is look for GetAwaiter() from the task; which fires an event basically when the task is complete. You can write a custom await object but you can't modify the C# language to give it the compiling performance gains. When I say await .. unless I return Task.FromResult immediately (which should only be done for checks not as a whole) then you will be invoking a new task. The new Task is a new thread (in fact it wraps the old thread internally). When you call await, the rest of the method gets wrapped with continuewith. That help? – Michael Puckett II Apr 12 '17 at 21:18
  • @VMAtm Tell you what.. I'll write up a full blown article on Task and then how async await keywords compile against Task for you if you like. Then if you want to dig and see if I'm wrong be my guest, but I'm certain of it. There's just not enough room in a comment to post the full blown understanding of it. – Michael Puckett II Apr 12 '17 at 21:21
  • Did you read the `There is no thread` about `I/O` operations? `await` *never* creates a new thread. It executes synchronously the part of method, meets the first `await` on it's way, all the way down to *real* bottleneck, as starting the http request or connecting to a database (as OP clearly wanted to), creates the state machine, and after that simply waits for a environment signal to continue the execution. *If* your operation is **CPU**-based, then it's a great idea to `Task.Run`, because it will provide the performance gain. – VMAtm Apr 12 '17 at 21:33
  • @MichaelPuckettII If @StephenCleary isn't a man who opinions matters for you, here goes another: Eric Lippert's answers about [`async/await` in ASP.NET](http://stackoverflow.com/a/39522149/213550) and about [difference between threading and tasks](http://stackoverflow.com/a/34681101/213550) – VMAtm Apr 12 '17 at 21:35
  • @VMAtm I suggest you re-read there is no thread and read the comments as well. There is INDEED a thread. Stephen was simply stating the disk write operation of async. Stephen himself, answers in the comments, that in normal situations there is a thread but for this situation, of a purest of async, there isn't. But again, you have experience opening the IL; check for yourself and then you can choose to agree or not. – Michael Puckett II Apr 12 '17 at 21:54
  • @VMAtm Also, Eric has a strongly opinionated version of articles as well.. more so that Stephen; and for the record I do admire and appreciate Stephen; Eric I can affirm and proven disagree with his article about the keyword volatile; and since then have not cared to endure any more of his articles. – Michael Puckett II Apr 12 '17 at 21:55
  • @VMAtm Here's the thing.. Honestly; take it how you want and I'll take mine how I want. The fact of the matter is I have broken it down to a core; myself; and witnessed what is happening. – Michael Puckett II Apr 12 '17 at 21:56
  • @VMAtm Hey; look. You are obviously very naïve to what I am saying... So, politely please examine my words. Task is an object. Internally it's purpose is to manage a thread, pass state, etc, and is scheduled via a TaskSheduler to run in an actual thread pool. Not the task but the thread the task creates. The await keyword DOES NOT create a new thread! I NEVER SAID THAT. await schedules the remaining task to run, on the current context (UI thread if you may) once the Task is complete. Anything AFTER await is scheduled to run on the same context. ... – Michael Puckett II Apr 12 '17 at 22:01
  • @VMAtm However; the Task or the Action or Func the task is pointing to is meant to run on a new thread. You CAN PARALLEL work with Tasks. I do it ALL THE TIME! When you create a task you can also configureawait to be false. In this case, the remaining work, AFTER await, is NOT scheduled to run on the current context. – Michael Puckett II Apr 12 '17 at 22:03
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/141592/discussion-between-michael-puckett-ii-and-vmatm). – Michael Puckett II Apr 12 '17 at 22:03
  • @VMAtm Finally... Please look back at my example answer. You can see the various ways the task works, which is meant to describe the behavior. If you don't believe there is a new thread then just use visual studio to monitor the threads. Start a task and see for yourself. There is a thread. – Michael Puckett II Apr 12 '17 at 22:04
  • Watch my hands, please, carefully. The OP asked about some remote request operations. We have a `HttpClient` for such tasks, and this class already has a Task-based methods, like `GetAsync`. Now, according to your code samples, we should call it with `Task.Run` or even create a `Task` and run it on our custom task factory. So, we have three choices: `await Task.Run(() => {})`, `await Task.Run(async => {})` or `await Task.Run(async () => await {})` – VMAtm Apr 12 '17 at 22:24
  • First and second are clearly wrong way to do that, so we choose a third one. Now we have 2 (two) task instead of 1 (one). Moreover, second one is waiting for a first one to fire the continuation after that. Your solution degrades the performance in this case, as it will need two threads to achieve the same result (or one thread, but twice long). That's what I'm [very naively] trying to explain to you. And you're continuously labeling me as novice, and laugh at me, and trying to explain **the truth sight` – VMAtm Apr 12 '17 at 22:29
  • @VMAtm No, no, no. My answer is not to tell anyone how to do anything. It is just to explain how a task works and what is going on with task and async await. It obviously makes the most sense to use the internal GetAsync; hence you would write await httpClient.GetAsync(myUrl); I am saying that the code, in that method, after the 'await httpClient.GetAsync()' is SCHEDULED to run on the SAME context that called await AFTER GetAsync is complete. And GetAsync is simply waiting on the returned event from the source before it returns the result and completes the task. – Michael Puckett II Apr 12 '17 at 22:32
  • @VMAtm I answered how he should write the task, if he wants to push it onto a new thread, at the bottom of my answer. – Michael Puckett II Apr 12 '17 at 22:34
  • So you've answered not the OP's question, but the one *you* think he was asking. Ok, nuff said. (And there is no UI thread in ASP.NET) – VMAtm Apr 12 '17 at 22:34
  • @VMAtm Please read the entire answer. I first explained the task because he was confused; hopefully to clarify how it works. Then I answered the question at the bottom. I did answer the question. Whether or not he wants to push it on a new thread is up to him and he would know his reasons. – Michael Puckett II Apr 12 '17 at 22:36
  • @VMAtm I know there is no UI thread in ASP.NET. Man look... Honestly; I think you're an amazing developer and you know your stuff. I have been doing this for a very long time and I honestly don't need to explain my position or work. But I assure you, that I am very certain of what I am talking about. – Michael Puckett II Apr 12 '17 at 22:37
  • @VMAtm However; I do see where the comments I said, UI thread in the final answer. Indeed it should have read same thread or current context. – Michael Puckett II Apr 12 '17 at 22:42
  • @VMAtm I have updated the answer to remove UI with the words Current thread in the answer at the bottom. That was a mistake. – Michael Puckett II Apr 12 '17 at 22:43
  • static void Main(string[] args) { new Parallelism().Start(); Console.Read(); } } public class Parallelism { public const int NumberOfLines = 50; public void Start() { var x = Run("x"); var y = Run("y"); var z = Run("z"); Task.WaitAll(x, y, z); } public Task Run(string text) => Task.Run(() => { for (var i = 0; i <= NumberOfLines; i++) Console.Write(text + " "); }); – Michael Puckett II Apr 12 '17 at 23:05
  • @VMAtm Try copying that comment above into a console app and run it many times; monitor the threads; and look at the output. There are other threads. – Michael Puckett II Apr 12 '17 at 23:06
  • @VMAtm Add this to the text and you can see the thread Id's. Thread.CurrentThread.ManagedThreadId – Michael Puckett II Apr 12 '17 at 23:10
  • @VMAtm Add this to the Main method and also to the text in run and you'll see they are background. Console.WriteLine(Thread.CurrentThread.IsBackground); – Michael Puckett II Apr 12 '17 at 23:11
  • And that proves that *console* app, which doesn't any synchronization context, correctly use thread pool. – VMAtm Apr 12 '17 at 23:34
  • @VMAtm It proves there are new threads from a task. What do you think a task is man? It wraps a thread and manages it for you. It runs that thread in a thread pool. The task's are stored in a pool. Copy it into an ASP.NET application or a WPF application or a UWP... You'll get the same results. – Michael Puckett II Apr 12 '17 at 23:37
  • Task do not create the thread, `TreadPool` does, if necessary. Task *executes* on a given thread, if it started on task scheduler which uses a thread pool. In ASP.NET you're already inside the ASP.NET thread pool, so `Task.Run` *should be* avoided there. – VMAtm Apr 12 '17 at 23:58
  • @VMAtm I'm done here. The point is there is a new thread when you use Task.Run or task = new Task... task.Start(); I already know how it works and I mentioned it "wraps" a thread. I never said the Task is a thread. Have it your way and either way, happy programming. – Michael Puckett II Apr 13 '17 at 01:04