Why does this code work synchronously?

I am trying to understand concurrency by doing this in code. I have a piece of code that I thought works asynchronously. But when I inserted debugging lines into it, I found that it works synchronously. Can someone explain what I need to do differently to push ComputeBB () to another thread using Task.Something?

Clarification . I want this code to run ComputeBB on another thread, so that the main thread continues to work without blocking.

Here is the code:

{ // part of the calling method Debug.WriteLine("About to call ComputeBB"); returnDTM.myBoundingBox = await Task.Run(() => returnDTM.ComputeBB()); Debug.WriteLine("Just called await ComputBB."); return returnDTM; } private ptsBoundingBox2d ComputeBB() { Debug.WriteLine("Starting ComputeBB."); Stopwatch sw = new Stopwatch(); sw.Start(); var point1 = this.allPoints.FirstOrDefault().Value; var returnBB = new ptsBoundingBox2d( point1.x, point1.y, point1.z, point1.x, point1.y, point1.z); Parallel.ForEach(this.allPoints, p => returnBB.expandByPoint(p.Value.x, p.Value.y, p.Value.z) ); sw.Stop(); Debug.WriteLine(String.Format("Compute BB took {0}", sw.Elapsed)); return returnBB; } 

Here is the result in the nearest window:

 About to call ComputeBB Starting ComputeBB. Compute BB took 00:00:00.1790574 Just called await ComputBB. 

Refinement If it were to execute asynchronously, it would be in the following order:

 About to call ComputeBB Just called await ComputBB. Starting ComputeBB. Compute BB took 00:00:00.1790574 

But this is not so.

Development The calling code has this signature: private static async Task loadAsBinaryAsync (string fileName) The next level up, however, I'm trying to stop using async. So, here is the stop call from top to bottom:

  static void Main(string[] args) { aTinFile = ptsDTM.CreateFromExistingFile("TestSave.ptsTin"); // more stuff } public static ptsDTM CreateFromExistingFile(string fileName) { ptsDTM returnTin = new ptsDTM(); Task<ptsDTM> tsk = Task.Run(() => loadAsBinaryAsync(fileName)); returnTin = tsk.Result; // I suspect the problem is here. return retunTin; } private static async Task<ptsDTM> loadAsBinaryAsync(string fileName) { // do a lot of processing Debug.WriteLine("About to call ComputeBB"); returnDTM.myBoundingBox = await Task.Run(() => returnDTM.ComputeBB()); Debug.WriteLine("Just called await ComputBB."); return returnDTM; } 
+8
multithreading c # task-parallel-library async-await task
source share
2 answers

I have a piece of code that I thought works asynchronously. But when I inserted the debug instructions into it, I found that it works synchronously.

await used to asynchronously wait for operations to complete. In doing so, it returns control to the calling method until it is complete.

what I need to do differently to push ComputeBB () to another thread

It is already running in the thread pool thread. If you do not want to wait on it in fire and forget mode asynchronously, do not express the await expression. Please note that this will affect exception handling. Any exception that occurs inside the provided delegate will be fixed inside the given Task , if you are not await , there is a chance that they will be processed without processing.

Edit:

Let's look at this piece of code:

 public static ptsDTM CreateFromExistingFile(string fileName) { ptsDTM returnTin = new ptsDTM(); Task<ptsDTM> tsk = Task.Run(() => loadAsBinaryAsync(fileName)); returnTin = tsk.Result; // I suspect the problem is here. return retunTin; } 

What you are doing now is synchronously blocked when using tsk.Result . Also, for some reason, you call Task.Run twice, once in each method. That is unnecessary. If you want to return your ptsDTM instance from CreateFromExistingFile , you will have to await it, this is not a work around. The performance of Fire and Forgetting does not care about the result at all. He simply wants to begin any operation he needs, if he fails or succeeds, it is usually not a concern. This is clearly not the case.

You will need to do something like this:

 private PtsDtm LoadAsBinary(string fileName) { Debug.WriteLine("About to call ComputeBB"); returnDTM.myBoundingBox = returnDTM.ComputeBB(); Debug.WriteLine("Just called ComputeBB."); return returnDTM; } 

And then somewhere up the call stack, you really don't need CreateFromExistingFiles , just call:

 Task.Run(() => LoadAsBinary(fileName)); 

If necessary.

Also, check out C # naming conventions that you currently don't follow.

+8
source share

await whole goal is to add synchronism back to asynchronous code. This makes it easy to separate parts that occur synchronously and asynchronously. Your example is absurd in that it never gets any benefits from this - if you just called the method directly and did not Task.Run it in Task.Run and await ing, then you would have the same result (with less overhead).

Consider this, though:

 await Task.WhenAll ( loadAsBinaryAsync(fileName1), loadAsBinaryAsync(fileName2), loadAsBinaryAsync(fileName3) ); 

Again, you have synchronization backward ( await functions as a synchronization barrier), but you performed three independent operations asynchronously with respect to each other.

Now there is no reason to do something similar in your code, since you are using Parallel.ForEach at the lower level - you are already using the CPU with the maximum (with unnecessary overhead, but do not pay attention to what is currently).

Thus, the basic use of await should actually handle asynchronous I / O, rather than working with the CPU - except for simplifying the code, which depends on the synchronization of some parts of the processor and some not (for example, you have four threads of execution that handle different parts of the problem at the same time, but at some point you need to reconnect to understand the individual parts - for example, look at the Barrier class). This includes things like “make sure that the user interface is not blocked when an intensive operation occurs with the processor in the background” - this makes the processor asynchronous with respect to the user interface. But at some point you still want to restore synchronization to make sure that you can display the results of the user interface.

Consider this winforms code snippet:

 async void btnDoStuff_Click(object sender, EventArgs e) { lblProgress.Text = "Calculating..."; var result = await DoTheUltraHardStuff(); lblProgress.Text = "Done! The result is " + result; } 

(note that the async void method, not the async Task and async Task<T> )

What happens is that (in the GUI thread) the label is first assigned the text Calculating... , then the asynchronous DoTheUltraHardStuff method DoTheUltraHardStuff scheduled, and then the method returns. Immediately. This allows the GUI thread to do whatever it needs. However, as soon as the asynchronous task is completed and the GUI is free to handle the callback, btnDoStuff_Click will continue with the result (or, of course, excluded) already indicated, back to the GUI stream, allowing you to set the shortcut to new text, including result of an asynchronous operation.

Asynchrony is not an absolute property - the material is asynchronous with some other things and synchronized with some other things. This only makes sense for some other things.

I hope you can now go back to the source code and understand the part that you misunderstood before. Of course, there are several solutions, but they depend a lot on how and why you are trying to do what you are trying to do. I suspect that you don’t need to use Task.Run or await at all - Parallel.ForEach already trying to distribute the CPU to several CPU cores, and the only thing you could do is make sure that the other code is not working, you do not need to wait for completion this work - which will make a lot of sense in a graphical application, but I don’t see how it would be useful in a console application for the sole purpose of computing this one thing.

So, you can really use await for the fire-and-forget code, but only as part of the code that does not prevent the execution of the code you want to continue. For example, you might have code like this:

 Task<string> result = SomeHardWorkAsync(); Debug.WriteLine("After calling SomeHardWorkAsync"); DoSomeOtherWorkInTheMeantime(); Debug.WriteLine("Done other work."); Debug.WriteLine("Got result: " + (await result)); 

This allows SomeHardWorkAsync to execute asynchronously with respect to DoSomeOtherWorkInTheMeantime , but not with await result . And of course, you can use await in SomeHardWorkAsync without having to parse the asynchrony between SomeHardWorkAsync and DoSomeOtherWorkInTheMeantime .

The GUI example shown above simply takes advantage of the continuation processing as something that happens after the task is completed, while ignoring the Task created in the async method (in fact, this is not so much the difference between using async void and async Task when ignoring the result). So, for example, to start and forget your method, you can use the following code:

 async void Fire(string filename) { var result = await ProcessFileAsync(filename); DoStuffWithResult(result); } Fire("MyFile"); 

This will cause the DoStuffWithResult to execute as soon as the result is ready, and the Fire method itself will return immediately after the ProcessFileAsync executed (up to the first await or any explicit return someTask ) ..

This template is usually not approved - there really is no reason to return void from the async method (except for event handlers); you could just as easily return Task (or even Task<T> depending on the scenario), and let the interlocutor decide whether he wants his code to be executed synchronously with respect to yours or not.

Yet again,

 async Task FireAsync(string filename) { var result = await ProcessFileAsync(filename); DoStuffWithResult(result); } Fire("MyFile"); 

does the same as when using async void , except that the caller can decide what to do with the asynchronous task. Perhaps he wants to run two of them in parallel and continue everything after that? It can just await Task.WhenAll(Fire("1"), Fire("2")) . Or he just wants it all to happen completely asynchronously with respect to his code, so he just calls Fire("1") and ignores the resulting Task (of course, ideally, you at least want to handle possible exceptions).

+2
source share

All Articles