I am trying to implement a system for closing all modal and non-modal windows in a WPF application (with the exception of the main application window.) When these windows are closed, any code awaiting the result of a dialog should be abandoned.
So far, I've considered/attempted two strategies:
Close and restart the application.
Close all the windows and rely on task cancellation exceptions to abandon all code that was waiting for dialog results. (It bubbles up to the App level and then becomes handled.)
The first solution definitely gets the application to close and will suffice for automatic logout, but I am extremely uncomfortable with code that continues to execute after the dialog it was waiting for has been closed. Is there a nice way to stop the execution of that code?
The second solution has been working relatively well (calling code is aborted) but has one critical flaw: on occasion, some combination of modal and non-modal windows closing in quick succession will cause the application to lock up on a ShowDialog call. (At least, when you pause execution, that's where it ends up.) This is strange because breakpoints clearly demonstrate that the Closed event is being raised on all windows that I intend to close. The result that the end user sees is a login screen that cannot be clicked on but can be tabbed into. So strange! Attempts to dispatch the call at different priorities have been unsuccessful, but a Task.Delay for 100ms might have done the trick. (That's not a real solution, though.)
If each open popup is awaiting a TaskCompletionSource in the background, and, upon completion of the TCS, tries to use the dispatcher to invoke Close on itself, why would one (or more) of the dialogs still be blocking on ShowDialog, even after seeing the Closed event be raised? Is there a way to properly dispatch these calls to Close so they complete successfully? Do I need to be particular about the order in which the windows close?
Some pseudocode-C#-hybrid examples:
class PopupService
{
async Task<bool> ShowModalAsync(...)
{
create TaskCompletionSource, publish event with TCS in payload
await and return the TCS result
}
void ShowModal(...)
{
// method exists for historical purposes. code calling this should
// probably be made async-aware rather than relying on the blocking
// behavior of Window.ShowDialog
create TaskCompletionSource, publish event with TCS in payload
rethrow exceptions that are set on the Task after completion but do not await
}
void CloseAllWindows(...)
{
for every known TaskCompletionSource driving a popup interaction
tcs.TrySetCanceled()
}
}
class MainWindow : Window
{
void ShowModalEventHandler(...)
{
create a new PopupWindow and set the owner, content, etc.
var window = new PopupWindow(...) { ... };
...
window.ShowDialog();
}
}
class PopupWindow : Window
{
void LoadedEventHandler(...)
{
...
Task.Run(async () =>
{
try
await the task completion source
finally
Dispatcher.Invoke(Close, DispatcherPriority.Send);
});
register closing event handlers
...
}
void ClosedEventHandler(...)
{
if(we should do something with the TCS)
try set the TCS result so the popup service caller can continue
}
}
With Window.ShowDialog you create a nested Dispather message loop. With await, it's possible to "jump" on that inner loop and continue the logical execution of an async method there, e.g.:
var dialogTask = window.ShowDialogAsync();
// on the main message loop
await Task.Delay(1000);
// on the nested message loop
// ...
await dialogTask;
// expecting to be back on the main message loop
Now, if dialogTask gets completed via TaskCompletionSource before the corresponding Window.ShowDialog() call returns to the caller, the above code may still end up on the nested message loop, rather than on the main core message loop. E.g., this may happen if TaskCompletionSource.SetResult/TrySetCanceled is called inside the dialog's Window.Closed event handler, or right before/after Window.Close() call. This may create undesired reentrancy side effects, including deadlocks, too.
By looking at your pseudocode, it's hard to tell where the deadlock may be. What is worrying is that you're using Task.Run only to await a task that completes on the main UI thread, or to invoke a synchronous callback on the main UI thread from a pool thread (via Dispatcher.Invoke). You certainly shouldn't need Task.Run here.
I use the following version of ShowDialogAsync for a similar purpose. It makes sure any inner message loops started by nested ShowDialogAsync calls exit before this particular ShowDialogAsync task is complete:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
// testing ShowDialogAsync
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var modal1 = new Window { Title = "Modal 1" };
modal1.Loaded += async delegate
{
await Task.Delay(1000);
var modal2 = new Window { Title = "Modal 2" };
try
{
await modal2.ShowDialogAsync();
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal2.Title);
}
};
await Task.Delay(1000);
// close modal1 in 5s
// this would automatically close modal2
var cts = new CancellationTokenSource(5000);
try
{
await modal1.ShowDialogAsync(cts.Token);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal1.Title);
}
}
}
/// <summary>
/// WindowExt
/// </summary>
public static class WindowExt
{
[ThreadStatic]
static CancellationToken s_currentToken = default(CancellationToken);
public static async Task<bool?> ShowDialogAsync(
this Window #this,
CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
var previousToken = s_currentToken;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(previousToken, token))
{
var currentToken = s_currentToken = cts.Token;
try
{
return await #this.Dispatcher.InvokeAsync(() =>
{
using (currentToken.Register(() =>
#this.Close(),
useSynchronizationContext: true))
{
try
{
var result = #this.ShowDialog();
currentToken.ThrowIfCancellationRequested();
return result;
}
finally
{
#this.Close();
}
}
}, DispatcherPriority.Normal, currentToken);
}
finally
{
s_currentToken = previousToken;
}
}
}
}
}
This allows you to cancel the most outer modal window via the associated CancelationToken, and that would automatically close any nested modal windows (those opened with ShowDialogAsync) and exit their corresponding message loops. So, your logical execution flow would end up on the correct outer message loop.
Note it still doesn't guarantee the correct logical order of closing multiple modal windows, if that matters. But it guarantees that the tasks returned by multiple nested ShowDialogAsync calls will get completed in the correct order.
I am not sure that this is gonna fix your issue, but in my case, I created extension methods to help mixing async code and window lifetime management. For example, you can create a ShowDialogAsync() that returns task that will complete when the window is actually closed. A CancellationToken can also be provided to close the dialog automatically if you request a cancellation.
public static class WindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window window, CancellationToken cancellationToken = new CancellationToken())
{
var completionSource = new TaskCompletionSource<bool?>();
window.Dispatcher.BeginInvoke(new Action(() =>
{
var result = window.ShowDialog();
// When dialog is closed, set the result to complete the returned task. If the task is already cancelled, it will be discarded.
completionSource.TrySetResult(result);
}));
if (cancellationToken.CanBeCanceled)
{
// Gets notified when cancellation is requested so that we can close window and cancel the returned task
cancellationToken.Register(() => window.Dispatcher.BeginInvoke(new Action(() =>
{
completionSource.TrySetCanceled();
window.Close();
})));
}
return completionSource.Task;
}
}
In your UI code, you would use the ShowDialogAsync() method like the following. As you can see, when the task is cancelled, the dialog is closed and an OperationCanceledException exception is thrown stopping the flow of your code.
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
YourDialog dialog = new YourDialog();
CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromSeconds(3));
await dialog.ShowDialogAsync(source.Token);
}
catch (OperationCanceledException ex)
{
MessageBox.Show("Operation was cancelled");
}
}
This is JUST for the first part of your issue (closing the windows).
if you do not need any of the results of the windows here is some simple code just to close all but the main window.
This is executing from my main window but you can change the if statement to look for your mainwindow instead if running from alternate area.
foreach(Window item in App.Current.Windows)
{
if(item!=this)
item.Close();
}
As for other threads I am unsure although as mentioned above if you have a list of handles to the threads then you should be able to traverse that as well and kill them.
Related
In a WPF app, we have a button that the user can click to trigger a list of videos to be loaded into VLC Media Player:
<Button Content="{Binding RotatorButtonLabel}" Command="{Binding RotateVideosCommand}" />
In the view model MainWindowVm, we have the command to handle the button click:
public ICommand RotateVideosCommand => new RelayCommand(RotateVideos);
private void RotateVideos()
{
IsRotatorActive = !IsRotatorActive;
RotatorButtonLabel = IsRotatorActive
? "Stop Rotator"
: "Rotate Videos";
_rotatorVm = new RotatorVm
{
ImageVms = ImagesView.Cast<ImageVm>().ToList(),
IsRotatorActive = IsRotatorActive
};
// This fires off a new thread to run the rotator, otherwise the UI freezes.
Task.Run(() => Messenger.Default.Send(rotatorVm, "LaunchRotator"));
}
Note in the above command handler, we use the MVVM Light Toolkit's Messenger to tell the code-behind to launch the rotator.
Now in the MainWindow.xaml.cs, we have the following c'tor:
private CancellationTokenSource _cancellationTokenSource = null;
private CancellationToken _cancellationToken;
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<RotatorVm>(this, "LaunchRotator", LaunchRotator);
// Other logic...
}
And then this what the above LaunchRotator calls:
private void LaunchRotator(RotatorVm rotatorVm)
{
if (_cancellationToken.IsCancellationRequested)
{
_cancellationTokenSource.Dispose();
}
if (_cancellationTokenSource == null || _cancellationToken.IsCancellationRequested)
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
}
if (!rotatorVm.IsRotatorActive)
{
_cancellationTokenSource.Cancel();
return;
}
RotateVideos();
}
private void RotateVideos()
{
while (true)
{
if (_cancellationToken.IsCancellationRequested)
{
return;
}
// This is to simplify the code and simulate work.
Thread.Sleep(5000);
}
}
If I click the "Stop Rotator" button, it can take a few seconds for the code to hit the next iteration of the while loop and read the IsCancellationRequested. How do I make it stop immediately in this scenario?
I've looked at this example, but it's assuming the task and the activities are all in one class; here, I have a view model and a code-behind. Thank you.
You can't (in a practical way) and shouldn't.
If you want to stop work in another thread then the proper way is to signal the thread (like you've done) and allow the thread to stop on it's own. Since your example workload is a Thread.Sleep(5000), once it's hit, that thread cannot do anything else until the sleep has expired. In other words, you can signal the thread properly and if it is sleeping, it will live until the sleep is complete, and then it will check the signal again.
Options:
In this case you can pass the Token to the simulated workload
using Task.Wait(5000, token) and using this instead of
Thread.Sleep(5000). This way the simulated work can also be
canceled.
For real work; you have to use your best judgement to test the signal
when and where it looks fair so that you're not waiting a long period
of time to return when cancel is signaled. Just note that making the caller wait while the
Thread or Task properly ends itself is the best way to do
it. This is why Thread.Abort gets so much criticism and is
discouraged.
Another option is to fire and forget. You can call Cancel() on the token source and then move on without waiting for the Task or Thread to complete. You have to design with this in mind but it's practical.
I've got a WinForms project that scans a given network and returns valid IP addresses. Once all the addresses are found, I create a user control for each and place it on the form. My functions to ping ip addresses use async and Task which I thought would "wait" to execute before doing something else, but it doesn't. My form shows up blank, then within 5 seconds, all the user controls appear on the form.
Declarations:
private List<string> networkComputers = new List<string>();
Here's the Form_Load event:
private async void MainForm_Load(object sender, EventArgs e)
{
//Load network computers.
await LoadNetworkComputers();
LoadWidgets();
}
The LoadNetworkComputers function is here:
private async Task LoadNetworkComputers()
{
try
{
if (SplashScreenManager.Default == null)
{
SplashScreenManager.ShowForm(this, typeof(LoadingForm), false, true, false);
SplashScreenManager.Default.SetWaitFormCaption("Finding computers");
}
else
Utilities.SetSplashFormText(SplashForm.SplashScreenCommand.SetLabel, "Scanning network for computers. This may take several minutes...");
networkComputers = await GetNetworkComputers();
}
catch (Exception e)
{
MessageBox.Show(e.Message + Environment.NewLine + e.InnerException);
}
finally
{
//Close "loading" window.
SplashScreenManager.CloseForm(false);
}
}
And the last 2 functions:
private async Task<List<string>> GetNetworkComputers()
{
networkComputers.Clear();
List<string> ipAddresses = new List<string>();
List<string> computersFound = new List<string>();
for (int i = StartIPRange; i <= EndIPRange; i++)
ipAddresses.Add(IPBase + i.ToString());
List<PingReply> replies = await PingAsync(ipAddresses);
foreach(var reply in replies)
{
if (reply.Status == IPStatus.Success)
computersFound.Add(reply.Address.ToString());
}
return computersFound;
}
private async Task<List<PingReply>> PingAsync(List<string> theListOfIPs)
{
var tasks = theListOfIPs.Select(ip => new Ping().SendPingAsync(ip, 2000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
I'm really stuck on why the form is being displayed before the code in the MainForm_Load event finishes.
EDIT
I forgot to mention that in the LoadNetworkComputers it loads a splash form which lets the user know that the app is running. It's when the form shows up behind that, that I'm trying to avoid. Here's a screenshot (sensitive info has been blacked out):
The reason one would use async-await is to enable callers of functions to continue executing code whenever your function has to wait for something.
The nice thing is that this will keep your UI responsive, even if the awaitable function is not finished. For instance if you would have a button that would LoadNetworkComputers and LoadWidgets you would be glad that during this relatively long action your window would still be repainted.
Since you've defined your Mainform_Loadas async, you've expressed that you want your UI to continue without waiting for the result of LoadNetWorkComputers.
In this interview with Eric Lippert (search in the middle for async-await) async-await is compared with a a cook making dinner. Whenever the cook finds that he has to wait for the bread to toast, he starts looking around to see if he can do something else, and starts doing it. After a while when the bread is toasted he continues preparing the toasted bread.
By keeping the form-load async, your form is able to show itself, and even show an indication that the network computers are being loaded.
An even nicer method would be to create a simple startup-dialog that informs the operator that the program is busy loading network computers. The async form-load of this startup-dialog could do the action and close the form when finished.
public class MyStartupForm
{
public List<string> LoadedNetworkComputers {get; private set;}
private async OnFormLoad()
{
// start doing the things async.
// keep the UI responsive so it can inform the operator
var taskLoadComputers = LoadNetworkComputers();
var taskLoadWidgets = LoadWidgets();
// while loading the Computers and Widgets: inform the operator
// what the program is doing:
this.InformOperator();
// Now I have nothing to do, so let's await for both tasks to complete
await Task.WhenAll(new Task[] {taskLoadComputers, taskLoadWidgets});
// remember the result of loading the network computers:
this.LoadedNetworkComputers = taskLoadComputers.Result;
// Close myself; my creator will continue:
this.Close();
}
}
And your main form:
private void MainForm_Load(object sender, EventArgs e)
{
// show the startup form to load the network computers and the widgets
// while loading the operator is informed
// the form closes itself when done
using (var form = new MyStartupForm())
{
form.ShowDialog(this);
// fetch the loadedNetworkComputers from the form
var loadedNetworkComputers = form.LoadedNetworkComputers;
this.Process(loadedNetworkComputers);
}
}
Now while loading, instead of your mainform the StartupForm is shown while the items are loaded.. The operator is informed why the main form is not showing yet. As soon as loading is finished, the StartupForm closes itself and loading of the main form continues
My form shows up blank, then within 5 seconds, all the user controls appear on the form.
This is by design. When the UI framework asks your app to display a form, it must do so immediately.
To resolve this, you'll need to decide what you want your app to look like while the async work is going on, initialize to that state on startup, and then update the UI when the async work completes. Spinners and loading pages are a common choice.
On button click I have fired call to StartContinuousThread which keeps polling the server every one second.
public class ThreadsWindow
{
CancellationTokenSource wtoken = new CancellationTokenSource();
private void btnStartTest_Click(object sender, RoutedEventArgs e)
{
StartContinuousThread();
}
void StartContinuousThread()
{
while(true)
{
var fact = Task.Factory.StartNew(() =>
{
CallServer();
Task.Delay(1000, wtoken.Token);
},
wtoken.Token);
}
}
}
StartContinuousThread starts executing, but btnStartTest_Click event handler finishes its execution.
How StartContinuousThread method would be able to update UI in this
case?
I wonder whether StartContinuousThread is also terminated with event handler, since there is no wait keyword for re-joining.
Please help!
If you goal is to poll the server every second you have a number of problem.
There is no loop. You execute the method once and then stop.
You create a task using Delay and then ignore it. You ought to be creating a continuation of that task to do the rest of the work to actually not do anything for a second.
Here is an implementation that addresses those issues:
private async Task StartContinuousThread(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
await Task.Run(() => CallServer());
await Task.Delay(1000, token);
}
}
Another possibility, especially if you're using an older version of C#, would be to use a timer to run some code every second.
As for updating the UI; you can do so freely anywhere outside of the call to Task.Run in this example. In your example you'd need to use some mechanism to marshal back to the UI thread, such as capturing the UI's synchronization context and posting to it.
I want to perform series of operations synchronously while closing my MVVM based WPF Application.
Right now I am using Task and Dispatcher.Invoke within the tasks to show the message to the user.
The issue is that when i used Dispatcher.Invoke method in the myfunction function, application gets stuck there. I know this function is working properly when I used these function other than closed event.
So is there any issue of using the Dispatcher.Invoke method in the Close event os the application. How can i solve this?
/// <summary>
/// Main window closing
/// </summary>
private void MainWindowClosing(object args)
{
var task1 = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
myfunction();
}).ContinueWith((cc) => { });
task1.Wait();
}
private void myfunction()
{
//my serries of operation will come here.
System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show("test");
}));
}
You are creating a Deadlock here. It won't work even when you put the code in button click handler.
REASON
You are creating a task and waiting on task using task1.Wait(). Hence UI dispatcher is waiting for task to complete before it can process any further messages.
At same time you are invoking one operation on UI dispatcher from task that too synchronously here
App.Current.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show("test");
}));
but since UI dispatcher is still waiting on task to complete, you can't invoke any method synchronously on it. Hence the DEADLOCK (waiting on each other).
Possible Solution
If you want to invoke task synchronously and that too without closing window, you can achieve that using following steps:
First of all remove the task1.Wait(). (do not block UI dispatcher)
Second, maintain bool to keep count that close event has been initiated.
Last, cancel closing event by setting e.Cancel = true and manually raise close event from task itself once you finished.
Relevant code:
bool closeInitiated = false;
private void poc_Closing(object sender, CancelEventArgs e)
{
if (!closeInitiated)
{
var task1 = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
myfunction();
}).ContinueWith((cc) => { });
closeInitiated = true;
e.Cancel = true;
}
}
private void myfunction()
{
App.Current.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show("test");
Close();
}));
}
I think the issue is at task1.Wait(); Go through this UI Thread Wait and DeadLock
I hope this will help.
I have gotten the same issue, and I figured the problem was that the Task where I'm invoking to, was already closed a second after clicking the Close button, so when the Invoking function takes a bit longer - it might crash.
If you still wanna execute this function, I used BeginInvoke instead of Invoke and it worked.
Here is my code:
if (app.Current.Dispatcher.CheckAccess()) //doesn't need to be invoked
myFunction();
else //coming from another thread need invoker
{
//at exiting program this task might already be canceled, so make sure it's not
if (!app.Current.Dispatcher.HasShutdownFinished)
app.Current.Dispatcher.Invoke(myFunction());
else
app.Current.Dispatcher.BeginInvoke(myFunction());
I've created a "progress/cancel" mechanism for my application whereby I can show a modal dialog while executing long-running operations, and have the dialog give some indication of progress. The dialog also has a cancel button to allow the user to cancel the operation. (Thanks to the SO community for helping me with this part).
Here's what the code looks like for running a dummy long-running operation:
public static async Task ExecuteDummyLongOperation()
{
await ExecuteWithProgressAsync(async (ct, ip) =>
{
ip.Report("Hello world!");
await TaskEx.Delay(3000);
ip.Report("Goodbye cruel world!");
await TaskEx.Delay(1000);
});
}
The parameters to the lamba are a CancellationToken and an IProgress. I'm not using the CancellationToken in this example, but the IProgress.Report method is setting the text for a label control on my progress/cancel form.
If I start this long-running operation from the button click handler on a form, it works fine. However, I've now discovered that if I start the operation from the click event handler for a ribbon button in a VSTO PowerPoint add-in, it fails at the second call to ip.Report (at the point where it tries to set the text of the label control). In this case, I get the dreaded InvalidOperationException saying that there's an invalid cross-thread operation.
There are two things that I find puzzling:
Why does the problem occur when the operation is invoked by clicking a button on the ribbon but not when invoked by clicking a button on a form?
Why does the problem occur at the second call to ip.Report but not at the first? I've not switched threads between those two calls.
You will of course want to see the rest of the code. I've tried to strip everything back to the bare bones:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncTestPowerPointAddIn
{
internal partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
public string Progress
{
set
{
this.ProgressLabel.Text = value;
}
}
private void CancelXButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
public static async Task ExecuteDummyLongOperation()
{
await ExecuteWithProgressAsync(async (ct, ip) =>
{
ip.Report("Hello world!");
await TaskEx.Delay(3000);
ip.Report("Goodbye cruel world!");
await TaskEx.Delay(1000);
});
}
private static async Task ExecuteWithProgressAsync(Func<CancellationToken, IProgress<string>, Task> operation)
{
var cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<string>();
var operationTask = operation(cancellationTokenSource.Token, progress);
// Don't show the dialog unless the operation takes more than a second
const int TimeDelayMilliseconds = 1000;
var completedTask = TaskEx.WhenAny(TaskEx.Delay(TimeDelayMilliseconds), operationTask).Result;
if (completedTask == operationTask)
await operationTask;
// Show a progress form and have it automatically close when the task completes
using (var progressForm = new ProgressForm())
{
operationTask.ContinueWith(_ => { try { progressForm.Close(); } catch { } }, TaskScheduler.FromCurrentSynchronizationContext());
progress.ProgressChanged += ((o, s) => progressForm.Progress = s);
if (progressForm.ShowDialog() == DialogResult.Cancel)
cancellationTokenSource.Cancel();
}
await operationTask;
}
}
}
The form itself simply has a label (ProgressLabel) and a button (CancelXButton).
The button click event handlers for both the ribbon button and the form button simply call the ExecuteDummyLongOperation method.
EDIT: More Information
At #JamesManning's request, I put in some tracing to watch the value of the ManagedThreadId, as follows:
await ExecuteWithProgressAsync(async (ct, ip) =>
{
System.Diagnostics.Trace.TraceInformation("A:" + Thread.CurrentThread.ManagedThreadId.ToString());
ip.Report("Hello world!");
System.Diagnostics.Trace.TraceInformation("B:" + Thread.CurrentThread.ManagedThreadId.ToString());
await TaskEx.Delay(3000);
System.Diagnostics.Trace.TraceInformation("C:" + Thread.CurrentThread.ManagedThreadId.ToString());
ip.Report("Goodbye cruel world!");
System.Diagnostics.Trace.TraceInformation("D:" + Thread.CurrentThread.ManagedThreadId.ToString());
await TaskEx.Delay(1000);
System.Diagnostics.Trace.TraceInformation("E:" + Thread.CurrentThread.ManagedThreadId.ToString());
});
This was interesting. When invoked from the form, the thread ID does not change. However, when invoked from the ribbon, I get:
powerpnt.exe Information: 0 : A:1
powerpnt.exe Information: 0 : B:1
powerpnt.exe Information: 0 : C:8
powerpnt.exe Information: 0 : D:8
So, the thread ID is changing when we 'return' from that first await.
I'm also surprised that we see "D" in the trace at all, since the call immediately prior to that is where the exception occurs!
This is the expected outcome if the current thread, the one that you called ExecuteDummyLongOperation() on, doesn't have a synchronization provider. Without one, the continuation after the await operator can only run on a threadpool thread.
You can diagnose this by putting a breakpoint on the await expression. Inspect the value of System.Threading.SynchronizationContext.Current. If it is null then there is no synchronization provider and your code will fail as expected when you update the form from the wrong thread.
It isn't entirely clear to me why you don't have one. You get a provider by creating a form on the thread, before calling the method. That automatically installs a provider, an instance of the WindowsFormsSynchronizationContext class. Looks to me like you create your ProgressForm too late.