0

I still meet issues calling async functions. In that code, I execute generateAllIfcs(dataFolder), then I would like to execute already addToExistingContract() or this.importNewContract() depending on contexte.SourceContract. But this line is not reached until generateAllIfcs(dataFolder) is finished.

private async void Import(object sender, RoutedEventArgs e)
{
    Task<bool> successGenerateIfc = this.generateAllIfcs(dataFolder);
    Task<bool> successAddContractToVsteel = contexte.SourceContract != null ?
        this.addToExistingContract() : this.importNewContract();
    await Task.WhenAll(successGenerateIfc, successAddContractToVsteel);
}

private async Task<bool> generateAllIfcs(string dataFolder)
{
    try
    {
        var progressIndicator4 = new Progress<int>(contexte.ReportProgress4);
        if (contexte.SourceContract != null)
        {
            int total4 = await contexte.NewModel.ExportNewIfcContract(
                contexte.SourceContract, progressIndicator4, 0, 100,
                contexte.SelectedConfigImportIFC, true,dataFolder);
        }
        else
        {
            int total4 = await contexte.NewModel.ExportNewIfcContract(null,
                progressIndicator4, 0, 100, contexte.SelectedConfigImportIFC,
                true, dataFolder);
        }
        return true;
    }
    catch
    {
        return false;
    }
}

public async Task<int> ExportNewIfcContract(Contract existingContract,
    IProgress<int> progress, int startProgress, int endProgress,
    ConfigImportIFC config, bool generateIfcAssAndRep, string dataFolder)
{
    int retour = await this.exportNewIfcContract(existingContract, progress,
        startProgress, endProgress,config, generateIfcAssAndRep, dataFolder);
    return retour;
}

private async Task<int> exportNewIfcContract(Contract existingContract,
    IProgress<int> progress, int startProgress, int endProgress,
    ConfigImportIFC config, bool generateIfcAssAndRep, string dataFolder)
{
    //some other calls to async functions
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34
  • I am not sure what you're trying to achieve here. Are you just trying to ensure that the tasks have finished execution or that they both returned `true` ? – Fabjan Nov 24 '22 at 09:28
  • My guess is that the problem is in `contexte.NewModel.ExportNewIfcContract`. If that takes a long time to return, that would cause what you're seeing. I can't see anything obviously incorrect with the code in your question – canton7 Nov 24 '22 at 09:32
  • if you mean the `Task.WhenAll` thing, I just expect both of them finish, after that I check what they returned. But the problem is on previous line, why doesn't it execute `Task successAddContractToVsteel = contexte.SourceContract != null ? this.addToExistingContract() : this.importNewContract();` inmediately, and waits for `this.generateAllIfcs(dataFolder)` to finish? – Siegfried.V Nov 24 '22 at 09:33
  • @canton7 but whatever happens in `ExportNewIfcContract`, isn't it supposed to continue whatever happens inside it? – Siegfried.V Nov 24 '22 at 09:35
  • 2
    `async` methods run synchronously up to the first `await`. If `contexte.NewModel.ExportNewIfcContract` does lots of heavy work before it runs the first `await`, then calling `await contexte.NewModel.ExportNewIfcContract(...)` will block – canton7 Nov 24 '22 at 09:39
  • 1
    @canton7 in fact, just read it in Marc's answer. So I guess this is the problem (`contexte.NewModel.exportNewIfcContract` runs a lot of things before the first await), will check it now. Thanks – Siegfried.V Nov 24 '22 at 09:41
  • Please try logging the [`IsCompleted`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.iscompleted) or the [`Status`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.status) property of your tasks (the `successGenerateIfc` and the `successAddContractToVsteel` tasks), immediately after creating each one of them. Then include your findings in the question. This information will be valuable for answering the question. – Theodor Zoulias Nov 24 '22 at 10:17
  • @TheodorZoulias excuse me, but I don't understand what you mean by ` logging the IsCompleted or the Status property of your tasks`? – Siegfried.V Nov 24 '22 at 13:13
  • I mean writing in a file, or in a database, or in the `Console`, what is the status of each `Task` immediately after its creation. And then reporting back this information. – Theodor Zoulias Nov 24 '22 at 13:31
  • If I add the `Yield`, the status is `Id = 8820, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}`. Without Yield I cannot see the status until the function is finished. Will try to ask a `Task.Run` instead of Yield, cause the `Yield` blocks the update of ProgressBar, didn't either understand why – Siegfried.V Nov 24 '22 at 14:06
  • @TheodorZoulias I will recheck my code before, then come back here after. The only modification I made was to add a 5th progressbar, but it is easy to have made a mistake somewhere else... – Siegfried.V Nov 24 '22 at 14:18
  • Siegfried we are not supposed to debug an ever changing piece of code. We are supposed to explain the behavior of your code, as shown in the question. In case the behavior has been sufficiently explained by the existing answer, then you don't have to do anything else here. In case you have a follow-up question, you could open a new question about it. – Theodor Zoulias Nov 24 '22 at 14:25
  • 1
    @TheodorZoulias ok, will then come back in another question in case, thanks (IN fact the main point was about async method runs synchronously until the first await. – Siegfried.V Nov 24 '22 at 14:27
  • *"Without Yield I cannot see the status until the function is finished."* -- This is expected. It would be very curious if you could see the status of the `Task`, before the method that creates the `Task` has completed. How could you see the status of something that doesn't even exist? So after the function is finished, and the `Task` is created, what is the status of the `Task`? – Theodor Zoulias Nov 24 '22 at 14:33
  • ok, so this is your question I didn't understand. Having the Yield, just after it is at "WaitingForActivation"(as expected). I also replaced the Yield by a `Task.Run` following your comments. In fact if we consider my original question, all is solved (I may not put start an async function with synchronous code at the beginning. Regarding the ProgressBar issue, it will be another question if I cannot solve it by myself. – Siegfried.V Nov 24 '22 at 15:02
  • The code in the question has neither `Task.Run` nor `Task.Yield`. This is the code with the behavior that needs to be explained. Whatever behavior appears after adding `Task.Run` or `Task.Yield` is not relevant to this question. My guess is that your code creates tasks that are already completed upon creation. If you add the line `Console.WriteLine(successGenerateIfc.IsCompleted)` after the line that creates the `successGenerateIfc` task, the output will be `true`. This is my guess, but only you can confirm or disprove it, by adding that line and reporting your findings. – Theodor Zoulias Nov 24 '22 at 18:16
  • 1
    Of course it doesn't have neither `Task.Run` nor `Task.Yield`, as I didn't know they were necessary, what Marc, Canton and yourself explained me. This is what solved my issue, question closed, thanks also. and yes, they all return true. – Siegfried.V Nov 24 '22 at 19:01
  • The `Task.Run` is not necessary when a method is genuinely asynchronous. The problem is that you have synchronous methods that pretend to be asynchronous (by having `Task` return type). The solution is to expose your methods for what they really are: by giving them a `bool` return type. Then, whenever you want to offload them to the `ThreadPool` for reasons of parallelization or UI-responsiveness, use the `Task.Run` like this: `Task task = Task.Run(() => syncMethod(arg));`, at the last possible moment. Don't hide the `Task.Run` in the implementation. – Theodor Zoulias Nov 24 '22 at 19:50
  • the method is asynchronous (you give me a doubt), just one part of the code is not. But inside I have 4 other async methods, doesn't it make it async method? – Siegfried.V Nov 24 '22 at 21:11
  • 1
    Based on this important information: *"and yes, they all return true"*, **all** your ✌async✌ methods are actually synchronous. Not a single one of them is genuinely asynchronous. They are all synchronous methods with asynchronous facades. That's why it is important to include this information inside the question: so that people can understand what's going on with your code, and give you better advice. You can still do it, it's not too late. – Theodor Zoulias Nov 24 '22 at 21:37
  • @TheodorZoulias I think I understood. After putting the beginning of my async function in a `Task.Run` , all is working asynchronously. I mean, they all work paralelly as expected. This was the only function that made problem, because of synchronous code at the beginning. I also badly explained : They all return true after the await. – Siegfried.V Nov 25 '22 at 12:19
  • You might find this interesting: ​​[Should I expose asynchronous wrappers for synchronous methods?](https://devblogs.microsoft.com/pfxteam/should-i-expose-asynchronous-wrappers-for-synchronous-methods/) by Stephen Toub. – Theodor Zoulias Nov 25 '22 at 14:15

1 Answers1

1

"async function not launching asynchronously" - yes, that's normal; async isn't about how things launch, it is about how they complete (or at least, continue); async doesn't mean "parallel", "concurrent", etc - and has only tangential relationship to threading; an async method runs synchronously until the first incomplete await, i.e. where the thing being awaited did not itself finish synchronously. Only for an incomplete await does the async engine unroll itself and register a continuation. I don't see anything inherently truly async in your code, although to be fair: I can't see exportNewIfcContract.

If you want concurrency, consider Task.Run etc to invoke a second execution flow. Or alternatively, consider adding:

await Task.Yield();

at the top of generateAllIfcs (the method that you intend to run concurrently); Task.Yield() always returns in an incomplete state, pushing the rest of the work as a continuation onto the thread-pool.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • If I understood completely your explaination, I think I got it : my `exportNewIfcContract` function launches lot of code, before launching some other async functions. If I got it, I just need to remove that 'non async code` inside an async sub function? – Siegfried.V Nov 24 '22 at 09:38
  • @Siegfried.V or you could just add `await Task.Yield();` at the top of `generateAllIfcs`; the entire point of `Task.Yield()` is that it is *always* incomplete - it forces the rest onto the thread-pool (added into answer) – Marc Gravell Nov 24 '22 at 09:40
  • `Task.Yield();` made the job, thanks. About Task.Run, I use it in some places of my code, but today, I still didn't understand how it works... – Siegfried.V Nov 24 '22 at 09:47
  • I was about to upvote, until I saw the `await Task.Yield();` suggestion. The recommended way of offloading work to the `ThreadPool` is the `Task.Run` method. The `Task.Yield` is a hack. It's the poor man's [`SwitchTo`](https://stackoverflow.com/questions/15363413/why-was-switchto-removed-from-async-ctp-release "Why was 'SwitchTo' removed from Async CTP / Release?"), which is inadvisable anyway. – Theodor Zoulias Nov 24 '22 at 10:11
  • @TheodorZoulias In fact I just understood what you wrote I think. My reportprogress bar isn't working since I used that `Task.Yield()`, so I guess I will have no choice learning that `Task.Run` thing... – Siegfried.V Nov 24 '22 at 10:15
  • @SiegfriedV the [`Task.Yield`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.yield) yields asynchronously back to the current synchronization context. The intended purpose of this method is unclear. Personally I don't know of any problem that can be solved reliably with this method. It's more of a solution asking for a problem, than the other way around. – Theodor Zoulias Nov 24 '22 at 10:26
  • @TheodorZoulias "I don't know of any problem that can be solved reliably with this method" ... this example *is* a scenario where it would be solved reliably with this method; could it also be solved with `Task.Run`? sure; and about a half dozen other ways; also note that the line above `Task.Yield` *literally says* "consider `Task.Run` etc to invoke a second execution flow" – Marc Gravell Nov 24 '22 at 12:42
  • Marc my point is that your answer could be improved by removing the `Task.Yield` suggestion. The `Task.Run` is perfectly capable of doing the offloading. Mentioning an alternative that works only accidentally, and under the condition that there is no ambient `SynchronizationContext` installed on the current thread, serves no other purpose than to confuse people. If you want to suggest the `Task.Yield` for something, please suggest it for it's intended purpose. Which I have no idea what it is, but I am 100% sure that it's not intended for offloading. – Theodor Zoulias Nov 24 '22 at 12:54
  • @MarcGravell So just for information, the ProgressBar issue is not beause of the `Yield`, this is because of an edit I made accidently. the `Yield` perfectly solved the problem, even if I replaced it by a `Task.Run` regarding Theodor's comments. (not sure it makes any difference in fact). Thanks again for today's lesson. – Siegfried.V Nov 24 '22 at 15:46