I feel that the answer to this is due to me having an incorrect concept of how threads work, but here goes.
private void button1_Click(object sender, EventArgs e
How is it possible to resume on a thread that is already running other code?
It needs to be specifically designed to support it. There needs to be some framework in place that allows the thread to take in work and to then execute that work at some later point in time.
This is how your UI thread works. It has a queue, and whenever you schedule work to be done in the UI thread you add an item to the end of the queue. The UI thread then takes the first item from the queue, executes it, and then when it's done, goes on to the next item, and so on, until you end your application.
What forces the thread to switch between the two sections of code that want to run?
Nothing, because it doesn't do that. It runs one, then when it finishes, it runs the other.
In general, what happens when you attempt to resume on a thread that is already running some other code?
Either someone wrote some custom code to specifically do just that, in which case, it does whatever that code specifically told it to do, or else you can't.
This is the secret that you do not understand: I give you the Windows Message Loop
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
while(TRUE)
{
bRet = GetMessage(&msg, NULL, 0, 0);
if (bRet <= 0) break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
This is the actual "main" of your application; you just don't see it because it is hidden behind the scenes.
A simpler loop could not be imagined. It gets a message from the queue. If there are no more messages then the program must be done. If there was a message then it runs the standard message translations and dispatches the message, and then keeps on running.
How is it possible to resume on a thread that is already running other code?
It isn't. "Resuming on a thread that is running other code" is actually putting a message in the queue. That "other code" is being synchronously called by DispatchMessage. When it is done, it returns to the loop, the queue is polled, and the message indicates what code needs to be dispatched next. That then runs synchronously until it returns back to the loop.
What forces the thread to switch between the two sections of code that want to run?
Nothing. That doesn't happen.
In general, what happens when you attempt to resume on a thread that is already running some other code?
The message that describes what continuation needs to be run is queued up.
I suppose this isn't any different to how my click event runs on the UI thread in the first place, in as much as I know it runs on the UI thread, and I know the UI thread is also doing other stuff, but I've not really thought about it like this before.
Start thinking about it.
Click events are exactly the same. Your program is doing something; you click the mouse; the click hander does not interrupt the UI thread and start running new work on it. Rather, the message is queued up, and when your UI thread control returns to the message loop, the click is eventually processed; DispatchMessage causes Button1_OnClick to be invoked via some mechanism in Windows Forms. That's what WinForms is; a mechanism for translating Windows messages into calls to C# methods.
But you already knew that. You know that when an event-driven program does a long-running synchronous operation, that the UI freezes, but that click events are processed eventually. How did you think that happened? You must have understood at some level that they were being queued up for processing later, right?
Exercise: What does DoEvents
do?
Exercise: Given what you now know: what could possibly go wrong if you call DoEvents
in a loop to unblock your UI?
Exercise: How is await
different from DoEvents
in a GUI application?