0

I'm having trouble with the below code. The async void method is a handler for an event in another part of my code, so the event invocation arrives here and this code can asynchronously handle it.

I am attempting to have runningTask represent the long-running stuff that may be happening in the background already. But the below shows that my understanding is off, as when I attempt to assign a value to runningTask, it immediately invokes the method.

The desired behavior is to assign HandleAsync() to runningTask, and then await it's long-running result. But the assignment is a method therefore immediately starts executing the method. I need to assign runningTask something to do (aka the logic inside of HandleAsync()), and then once assigned, execute that logic at the await statement. Can you demonstrate how to refactor this (and fix my incorrect understanding of how to express this)

        static XDocument document{get;set;}
        static ConcurrentQueue<Args> backlog = new ConcurrentQueue<Args>();
        static Task runningTask {get;set;}

        static async Task HandleAsync(XDocument document, Args e, CancellationToken token)
        {
            // await a long-running XML doc modification
            // await a long running file write
        }

        static async void Event_Invoked(Args e)
        {

            if (runningTask is Task t && t.Status == TaskStatus.Running)
            {
                backlog.Enqueue(e);
                return;
            }

            //This assignment is an invocation of the method.
            runningTask = HandleAsync(document, e,tokenSource.Token); 

            await runningTask.ConfigureAwait(false);           
        }
NWoodsman
  • 423
  • 4
  • 10
  • `runningTask = HandleAsync(...)` calls the method *first*, and then assigns the returned task. If `HandleAsync` does not return quickly it might not be truly async, or might do significant work synchronously. You could try inserting a `await Task.Yield()` at the start of `HandleAsync`, but I would probably suggest starting with some measurements/profiling to check what actually takes time. – JonasH Mar 30 '22 at 18:18
  • Related : [Check if task is already running before starting new](https://stackoverflow.com/questions/19197376/check-if-task-is-already-running-before-starting-new) / [Async always WaitingForActivation](https://stackoverflow.com/questions/20830998/async-always-waitingforactivation) – Theodor Zoulias Mar 30 '22 at 19:39
  • @TheodorZoulias Yes that's related, but the summary of my problem is that I don't see how to encapsulate the content of a method inside a Task in a way that's not a method invocation. – NWoodsman Mar 30 '22 at 20:33
  • NWoodsman do you mean that you want to create a `Task` that is not started immediately? That you want to defer to execution of the task for some later moment? – Theodor Zoulias Mar 30 '22 at 21:35
  • @TheodorZoulias Well, kinda. I need `runningTask` to be associated with the content of `HandleAsync()` without actually invoking the `HandleAsync()` method. It looks like maybe I need to refactor `HandleAsync()` into `Action`. Is it correct to assume that given a `new Task(Action)` calling `Task.Run` is exactly when `Action.Invoke()` is called? – NWoodsman Mar 30 '22 at 21:51
  • @TheodorZoulias Therefore the `Action` isn't invoked until the `await runningTask` invocation. That's the only deference needed. – NWoodsman Mar 30 '22 at 21:57
  • @NWoodsman `I need to assign, and execute at the await statement.` - why? – Stephen Cleary Mar 30 '22 at 21:59
  • @StephenCleary I fixed that sentence so as not to confuse the intent. – NWoodsman Mar 30 '22 at 22:04
  • @NWoodsman: Doesn't really answer my question. `I need to assign runningTask ... and then once assigned, execute that logic at the await statement.` - why? – Stephen Cleary Mar 30 '22 at 22:07
  • NWoodsman an async operation that is executed when it is awaited is known as `AsyncLazy`. Take a look at this question for more info: [Enforce an async method to be called once](https://stackoverflow.com/questions/28340177/enforce-an-async-method-to-be-called-once). Creating cold tasks with the `Task` constructor and later starting them with the `Start` method is surprisingly nuanced. There are many traps in the way, and it's likely that you'll fall in every single one of them before you learn how to avoid them. Proceed with caution! – Theodor Zoulias Mar 30 '22 at 22:20
  • @StephenCleary When the `Event_Invoked()` method is called by the event, the `runningTask` might be running in the background doing it's job of building an XML tree, writing to a file, etc. As you can see, I have some cursory checking in place to see if the `runningTask` is running. If the task is not running, then we simply need to start `runningTask`. _It is `null` by default, which therefore means it needs to be initialized and then started running (with the objective of doing all the stuff in `HandleAsync()`)._ Everything italicized is what I have trouble with. – NWoodsman Mar 30 '22 at 22:46
  • @NWoodsman: I still don't understand why it has to be assigned before it's running. Why not just assign it to the task returned from the method? How does this break? As long as you're talking about a *UI* event, it can't be called concurrently (if that's what you're worried about). – Stephen Cleary Mar 31 '22 at 00:52

0 Answers0