-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Awaiting a queue not working as expected. #4
Comments
Hey @RossMetacraft, just letting you know I've just seen this issue. I'll need to find some time to reproduce the problem and form a response. To help with that, would you mind providing a bit more context around your application? Is it using WPF or Windows Forms or is this inconsequential? |
Hi @borland, the test app that I posted above to illustrate the issue is a WPF app, but my actual application is a background service running within an ASP.net core application. Thanks for taking a look. |
I haven't been able to run your example, but I've had a think and I'm pretty sure I know what's happening here. The serial queue is working as designed, but perhaps not as you expect; threading and concurrency is full of pitfalls like this. I don't know your level of familiarity with The serial queue offers the same kind of model as the WPF dispatcher thread does, or the nodejs main eventloop. Each queue makes the following guarantees:
But what exactly constitutes an "operation" is not always obvious. Let's take a look at your IterateInts function private async Task IterateInts()
{
await mSerialQueue;
foreach (int i in mList) {
System.Diagnostics.Debug.WriteLine($"iterating: {i}");
await Task.Delay(500);
}
} The way Anyway, a useful mental model is more like this: private async Task IterateInts()
{
switch(state)
{
case 0:
mSerialQueue.RunCallback(/* re-enter the function but with state set to 1 */);
return;
case 1:
foreach (int i in mList) {
System.Diagnostics.Debug.WriteLine($"iterating: {i}");
Task.Delay(500).ThenRunCallback(/* re-enter the function but with state set to 2 */);
return;
case 2:
// tail end of the loop.
// That's right, the switch case is in the middle of the loop, so it goes round again
}
}
} What we can see here is that the function returns and then gets re-entered after the asynchronous thing. During the gaps (e.g while waiting for Task.Delay), there is no code running. So your bug is:
Or, put in a different way: Your Two fixes:
I hope that helps, thanks! |
Hi again @borland, thank you so much for the detailed explanation. I did already have a fairly good understanding of how async/await works and how it creates a state machine for executing your async method as a series of steps, but it's good to see it explained as you did. If I'm understanding you correctly, when you await the queue, you are essentially saying "wait until the queue is free". Put another way, awaiting the queue is short hand for wrapping the rest of the method in an action and passing that action to DispatchAsync(). Is that right? In my test app, what I was expecting was that the serial queue would guarantee that the If all of the above is correct, then it makes me wonder why the examples in the main Readme show asynchronous stuff happening while execution is "on the queue". Specifically, this bit: SerialQueue m_queue = new SerialQueue();
// imagine this is a WPF or winforms app
public void Button_Click()
{
// here we are on the UI main thread
var result = await DoBackgroundProcessing();
// and we are still on the UI thread because 'await DoBackgroundProcessing' captured the sync context.
MyTextBox.Text = result;
}
private async Task<string> DoBackgroundProcessing()
{
// at this point we are still on the UI main thread
await m_queue;
// now we are OFF the main UI thread and onto the serial queue (behind the scenes we're on a threadpool thread)
var response = await SendNetworkRequest();
// still on the serial queue
return response;
} In this example, wouldn't the awaited call to I think I'm probably expecting too much of the concept of a "serial queue". I'm expecting it to run the tasks in order, never more than one at a time. In other words, each task fully completes before the next begins. It sounds like it really only guarantees that no two tasks will run at the same time on two separate threads. Two tasks may still run at the same time, in the sense that task B may be started before task A completes, if task A is async and contains an How am I doing? 😄 |
Hello! I came across this library while searching for a way to run tasks in a serial fashion to avoid any concurrency issues in my application. It looks like it will work perfectly. In your Examples and Programming Models Wiki page, you demonstrate a very simple and elegant way of achieving serialization of tasks by simply awaiting the queue at the start of each task method. I tried doing this in a simple proof-of-concept application and it does not appear to be working as I expect. I'm either doing something wrong, or this library isn't actually designed to do what I thought. I'm hoping you can set me straight.
In my simple test app, I have a window with two buttons. One button adds random integers to a list. The other button iterates over the list, printing the values to the debug console. It sleeps for 500 ms between prints. I'm hoping to be able to populate the list with a few integers, then iterate the list, and try to add another integer to the list while the list is being iterated, and avoid the usual InvalidOperationException due to the collection being modified during iteration.
Here is the window's code-behind file:
To run the test, I launch the app, click the "Add" button 5 times to populate the list with 5 random integers. I then click the "Iterate" button, and while it is iterating, I click the "Add" button again to attempt to add a 6th integer to the list. This causes the usual InvalidOperationException with the message "Collection was modified; enumeration operation may not execute."
Am I missing something, or does the lib not behave the way I'm expecting here?
Thank you!
The text was updated successfully, but these errors were encountered: