Async Showdialog

C# Modal dialog box (ShowDialog or MessageBox.Show) in async method not works as expected

Solution for problem-1:
ShowDialogAsync extension method solves the problem.

Solution for problem-2:

        private async void button1_Click(object sender, EventArgs e)
{

var handle = this.Handle;
await System.Threading.Tasks.Task.Run(() =>
{
System.Threading.Thread.Sleep(2000);

//Solution for Problem2:

NativeWindow win32Parent = new NativeWindow();
win32Parent.AssignHandle(handle);
//Works as expected (Topmost and Modal):
MessageBox.Show(win32Parent, "Test");
});
}

Related topic

How can I use showDialog with await

Not sure if there is a better way, but the following appears to work. Note the "then" clause on the call to showDialog().

void _showAlert3(BuildContext context, String text, int seconds) async {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text("Error"),
content: Text(text),
)).then((val) {});
await Future.delayed(Duration(seconds: seconds));
Navigator.of(context).pop(true);
}

As for trolls, RTFQ ("structured so that this can be done in one function") and if you don't want to help then go away.

WPF - Task.Run(() = window.ShowDialog) fails

So the "real" question: How can I show a blocking Window when a long running task is running and closing it after the long running task has finished and, eventually, inform the window about the progress of the action.

You've already got most of the pieces; you just need to put them together.

How can I show a blocking Window

All UI should go on a single GUI thread. This isn't strictly necessary, but it's a great simplifier and works for the vast, vast majority of applications. A "blocking Window" is known in the UI world as a "modal dialog", and you show one by calling ShowDialog.

// Start the long-running operation
var task = LongRunningOperationAsync();

// Show the dialog
progressWindow.ShowDialog();

// Retrieve results / propagate exceptions
var results = await task;

closing it after the long running task has finished

For this, you need to wire up the completion of the task to close the window. This is pretty straightforward to do using async/await:

async Task DoOperationAsync(ProgressWindow progressWindow)
{
try
{
await LongRunningOperationAsync();
}
finally
{
progressWindow.Close();
}
}

// Start the long-running operation
var task = DoOperationAsync(progressWindow);

// Show the dialog
progressWindow.ShowDialog();

// Retrieve results / propagate exceptions
var results = await task;

inform the window about the progress of the action

Assuming your operation is using the standard IProgress<T> interface for reporting progress:

async Task DoOperationAsync(Window progressWindow, IProgress<int> progress)
{
try
{
await LongRunningOperationAsync(progress);
}
finally
{
progressWindow.Close();
}
}

var progressWindowVM = ...;
var progress = new Progress<int>(value =>
{
progressWindowVM.Progress = value;
});

var task = DoOperationAsync(progressWindow, progress);
progressWindow.ShowDialog();
var results = await task;

Another common use case to consider is the cancelling of the operation if the user closes the progress dialog themselves. Again, this is straightfoward if your operation is already using the standard CancellationToken:

async Task DoOperationAsync(Window progressWindow, CancellationToken token, IProgress<int> progress)
{
try
{
await LongRunningOperationAsync(token, progress);
}
catch (OperationCanceledException) { }
finally
{
progressWindow.Close();
}
}

var progressWindowVM = ...;
var progress = new Progress<int>(value =>
{
progressWindowVM.Progress = value;
});

var cts = new CancellationTokenSource();
progressWindow.Closed += (_, __) => cts.Cancel();

var task = DoOperationAsync(progressWindow, cts.Token, progress);
progressWindow.ShowDialog();
var results = await task;

The solution should (preferably) work with the MVVM pattern and not rely on (too much) code behind.

MVVM works great within a single window. As soon as you start trying to data-bind window-level actions and attributes, a lot of it falls apart. This is not due to MVVM being a poor pattern, but rather just that a lot of MVVM frameworks do not handle this well.

The example code above only uses data binding to report progress to the progress dialog. If your MVVM framework can data-bind the showing/hiding of a modal window, then you could use my NotifyTaskCompletion type to drive that. Also, some frameworks have a more elegant (MVVM) way to handle Window.Closed, but the details depend on your framework.

Button.DialogResult, Form.ShowDialog and async event handlers out of -expected- order execution

This is expected behaviour, as you wait asynchronously for the Task.Delay task to finish. While that is being done, the method returns to its caller, and this is by design. Take this example:

private void button1_Click(object sender, EventArgs e)
{
method();
textBox1.Text += "C";
}

async void method()
{
textBox1.Text += "A";
await Task.Delay(1000);
textBox1.Text += "B";
}

This is pretty much the same situation, the text of the TextBox will be ACB after pressing button1, as the method method() returns to its caller when await Task.Delay(1000); is called.

To get the desired output, do it as Hans Passant suggested in the comments: Set the DialogResult property of the button to None, so that the method does not return to the Form1. Then, whenever you wish to hand control over to Form1 again, simply call this.DialogResult = DialogResult.OK in the method on runtime, which will have the same effect.

Continue code execution after calling ShowDialog()

As the author of the linked answer, I feel summoned :) I think I understand the problem you're running into. Try the following, see the comments inline:

// on the main message loop of the UI thread
var LoadingTask = LoadingWindow.ShowDialogAsync();

// dialog isn't shown here yet, yield to the message loop
await Task.Yield();

// now ShowDialog has been called and the dialog is visible,
// we're now on the nested message loop run by ShowDialog
await Task.Run(() =>
{
// on a thread pool thread, do the work here
Thread.Sleep(2000);
});

// on the UI thread, still on the nested message loop of ShowDialog
// close the dialog
LoadingWindow.Close();

// still on the nested message loop, despite the window has closed
await LoadingTask;

// this is when ShowDialog method has returned,
// and we're back to the main message loop of the UI thread

Don't use Thread.Sleep or any other long-running synchronous code on the UI thread, even when experementing. The dialog is shown asynchronously. Thread.Sleep blocks the message loop and the ShowDialog isn't getting a chance to execute when you expect it to.

It helps to understand what Task.Yield actually does behind the sence in this case. The continuation code after await Task.Yield() won't get magically executed until after some future iterations of the message loop. And there will be no future iterations while you're making a blocking call on the UI thread. That's what the thread pool and Task.Run are for.

Otherwise, the continuation callback will be just sitting in the queue managed by WindowsFormsSynchronizationContext or DispatcherSynchronizationContext.

Flutter awaiting result of dialog

You can add await keyword in-front of showdialog and return value at the end of show dialog.

added await.

  await showDialog(

add return value

 barrierDismissible: false);
return true; // added line

Show modal dialog while performing an asynchronous operation

As discussed in the comments, you are simply using the wrong Control reference to call BeginInvoke() on. The dialog cannot work, it wasn't created yet. Which is important, .NET cannot figure out what thread owns the window until the window is committed to a specific thread. Which doesn't happen until you call Show().

But any other window will do, you must already have one since it isn't legal or practical to display a dialog without an owner. You must provide a constructor or a property so the client code can provide a reference to such a window. Any will do, the main window of the app is a practical choice.

You can completely separate a class from the UI implementation (not practical here since you display a dialog) by copying the value of Synchronization.Current in the constructor. On which you later can call its Post() method. All UI class libraries in .NET install a synchronization provider that will marshal the call.



Related Topics



Leave a reply



Submit