0

Basic overview: program should launch task to parse some array of data and occasionally enqueue tasks to process it one at a time. Test rig have a button an two labels to display debug info. TaskQueue is a class for SemaphoreSlim from this thread

Dispatcher dispath = Application.Current.Dispatcher;

async void Test_Click(s, e)
{
   TaskQueue queue = new TaskQueue();

   // Blocks thread if SimulateParse does not have await inside
   await SimulateParse(queue);

   //await Task.Run(() => SimulateParse(queue));

   lblStatus2.Content = string.Format("Awaiting queue"));
   await queue.WaitAsync(); //this is just SemaphoreSlim.WaitAsync() 

   lblStatus.Content = string.Format("Ready"));
   lblStatus2.Content = string.Format("Ready"));
   MessageBox.Show("Ok");
}

async Task SimulateParse(TaskQueue queue)
{
    Random rnd = new Random();
    int counter = 0; // representing some piece of data 
    for(int i = 0; i < 500; i++)
    {
        dispatch.Invoke(() => lblStatus2.Content = string.Format("Check {0}", ++counter));

        Thread.Sleep(25); //no await variant
        //await Task.Delay(25);
        
        // if some condition matched - queue work 
        if (rnd.Next(1, 11) < 2)
        {
            // Blocks thread even though Enqueue() has await inside
            queue.Enqueue(SimulateWork, counter); 

            //Task.Run(() => queue.Enqueue(SimulateWork, counter));
        }
    }   
}

async Task SimulateWork(object par)
{
    dispatch.Invoke(() => lblStatus.Content = string.Format("Working with {0}", par));

    Thread.Sleep(400); //no await variant
    //await Task.Delay(400);            
}

It seems, that it works only if launched task have await inside itself, i.e. if you trying to launch task without await inside it, it will block current thread.

This rig will work as intended, if commented lines are used, but it looks like excessive amount of calls, also, real versions of SimulateParse and SimulateWork does not need to await anything. Main question is - what is the optimal way to launch task with non-async function inside of it? Do i just need to encase them in a Task.Run() like in commented rows?

Efgrafich
  • 7
  • 3
  • 1
    What does the `dispatch.Invoke` do? Is dispatch an event? is it a field containing the current dispatcher? – Jeroen van Langen Apr 01 '22 at 07:01
  • 2
    `await queue.WaitAsync();` -- The linked class `TaskQueue` does not have a public `WaitAsync` method. Are you sure that all this complexity is required in order to demonstrate the blocking behavior of an async method without `await`? The compiler should give you a warning for doing that, with a quite explanatory message. – Theodor Zoulias Apr 01 '22 at 07:06
  • As an aside, I don't see what `TaskQueue` is adding here. Why not just `await SimulateWork(counter)`? However, it sounds like neither `SimulateParse` nor `SimulateWork` should be `async` at all - you should just be calling `await Task.Run(() => SimulateParse())` in your handler to run your (synchronous) work on the thread pool. – Charles Mager Apr 01 '22 at 09:17
  • 1
    Be clear - `await` doesn't "launch" anything. You somehow, anyhow, end up with a running `Task` and that's what you `await`. How you got that `Task`, how it completes, whether a thread is involved are all details that are irrelevant to the `await` statement. That you often create such a `Task` on the same line by e.g. calling a genuinely `Async` method is coincidence. – Damien_The_Unbeliever Apr 01 '22 at 12:13
  • `TaskQueue` is used here to run task one by one, such as printer queue. Yes, `SimulateParse` and `SimulateWork` don't need to be async themselves, i mentioned that in last paragraph. – Efgrafich Apr 01 '22 at 12:20

1 Answers1

2

TaskQueue is used here to run task one by one

It will run them one at a time, yes. SemaphoreSlim does have an implicit queue, but it's not strictly a FIFO-queue. Most synchronization primitives have a mostly-but-not-quite-FIFO implementation, which is Close Enough. This is because they are synchronization primitives, and not queues.

If you want an actual queue (i.e., with guaranteed FIFO order), then you should use a queue, such as TPL Dataflow or System.Threading.Channels.

if you trying to launch task without await inside it, it will block current thread.

All async methods begin executing on the current thread, as described on my blog. async does not mean "run on a different thread". If you want to run a method on a thread pool thread, then wrap that method call in Task.Run. That's a much cleaner solution than sprinkling Task.Delay throughout, and it's more efficient, too (no delays).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810