Show and Close form from a background thread - c#

I have a windows form (AlertForm) that displays a progress bar. I am trying to show it an close it thru a task. The problem that I am having is when I spawn a thread and call winforms.showdialog() it holds the thread and therefore cant cancel it. I am doing this because I am writing an excel add in thru c# in which I don't have a panel to show a progress bar.
static void Main(string[] args)
{
AlertForm alert = new AlertForm();
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task.Factory.StartNew(() =>
{
//while (true)
//{
// do some heavy work here
alert.ShowDialog();
Thread.Sleep(100);
if (ct.IsCancellationRequested)
{
alert.Close();
// another thread decided to cancel
Console.WriteLine("task canceled");
//break;
}
//}
}, ct);
// Simulate waiting 3s for the task to complete
Thread.Sleep(3000);
// Can't wait anymore => cancel this task
ts.Cancel();
Console.ReadLine();
}
How do I open the form and not hold it so at a later stage when a long task is complete I can cancel the task which will close the window
(AlertForm)?

If the AlertForm class is inheritance from Windows.Forms.Control then you can use Invoke method
alert.Invoke((MethodInvoker)delegate ()
{
alert.Close();
});

Related

async/await running on single thread

I have a console application where in some instances a user interface needs to be presented. This user interface needs to remain responsive as it will contain a loading gif, progress bar, cancel button etc. I have the following sample code:
class Program
{
static void Main(string[] args)
{
DoWork().GetAwaiter().GetResult();
}
private static async Task DoWork()
{
TestForm form = new TestForm();
form.Show();
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form.Close();
}
}
}
I would expect from the code above for the TestForm to be displayed for approximately 5 seconds before being closed due to the value of the string being "Plop", however all that happens is the Task is run and the if statement is never reached. Furthermore the UI of the TestForm does not remain responsive. What is wrong with this code?
So I've managed to hack together a dirty solution for this. It is not a clean solution so I'm still open to suggestions but for what I need it works fine
private static void DoWork()
{
TestForm form = new TestForm();
Task formTask = Task.Run(() => form.ShowDialog());
Task<string> testTask = Task.Run(() =>
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine(i.ToString());
}
Console.WriteLine("Background task finished");
return "Plop";
});
Console.WriteLine("Waiting for background task");
testTask.Wait();
if (testTask.Result == "Plop")
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
}
Console.WriteLine("App finished");
}
This outputs 'Waiting for background task' first, followed by the number count of the Task and then outputs 'Background task finished' when the long process is complete, as well as closes the responsive UI form
Its a classic deadlock.When your code hit await ,control goes back to main thread which is a blocking wait for DoWork GetResult(); When Task.Run thread is finished controls tries to go back to main thread but its waiting for DoWork to be finished. That is the reason last If statement never executes.
But apart from deadlock ,there is also one more issue in your code which will make your UI freeze.Its the form.Show() method.If you remove everything related to async-await and only use form ,it will still freeze.The problem is Show method expects a windows message loop which will be provided if you create a Windows.Forms application but here you are launching form from console application which doesnt have a message loop. One solution would be to use form.ShowDialog which will create its own message loop. Another solution is to use System.Windows.Forms.Application.Run method which provides a win messages loop to the form created through thread pool thread. I can give you one possible solution here but its up to you how you structure your code as the root cause is identified.
static void Main(string[] args)
{
TestForm form = new TestForm();
form.Load += Form_Load;
Application.Run(form);
}
private static async void Form_Load(object sender, EventArgs e)
{
var form = sender as Form;
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form?.Close();
}
}
Ok I did mark my first answer to be deleted, since what I put there works for WPF and not for you require, BUT in this one is doing what you asked, I did try it and opens the WinForm then closes after 5 seconds, here is the code:
static void Main(string[] args)
{
MethodToRun();
}
private static async void MethodToRun()
{
var windowToOpen = new TestForm();
var stringValue = String.Empty;
Task.Run(new Action(() =>
{
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
windowToOpen.Show();
}).Wait();
System.Threading.Thread.Sleep(5000);
stringValue = "Plop";
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
if (String.Equals(stringValue, "Plop"))
{
windowToOpen.Close();
}
}).Wait();
})).Wait();
}

How to use CancellationTokenSource to close a dialog on another thread?

This is related to my other question How to cancel background printing.
I am trying to better understand the CancellationTokenSource model and how to use it across thread boundaries.
I have a main window (on the UI thread) where the code behind does:
public MainWindow()
{
InitializeComponent();
Loaded += (s, e) => {
DataContext = new MainWindowViewModel();
Closing += ((MainWindowViewModel)DataContext).MainWindow_Closing;
};
}
which correctly calls the CloseWindow code when it is closed:
private void CloseWindow(IClosable window)
{
if (window != null)
{
windowClosingCTS.Cancel();
window.Close();
}
}
With the selection of a menu item, a second window is created on a background thread:
// Print Preview
public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
// Was cancellation already requested?
if (ct.IsCancellationRequested)
ct.ThrowIfCancellationRequested();
...............................
// Use my custom document viewer (the print button is removed).
var previewWindow = new PrintPreview(fixedDocumentSequence);
//Register the cancellation procedure with the cancellation token
ct.Register(() =>
previewWindow.Close()
);
previewWindow.ShowDialog();
}
}
In the MainWindowViewModel (on the UI thread), I put:
public CancellationTokenSource windowClosingCTS { get; set; }
With its constructor of:
// Constructor
public MainMenu()
{
readers = new List<Reader>();
CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
windowClosingCTS = new CancellationTokenSource();
}
Now my problem. When closing the MainWindow on the UI thread, windowClosingCTS.Cancel() causes an immediate call to the delegate registered with ct, i.e. previewWindow.Close() is called. This now throws immediately back to the " If (Windows != null) with:
"The calling thread cannot access this object because a different
thread owns it."
So what am I doing wrong?
Your problem is that your preview window runs on another thread. When you trigger cancellation, you execute the registered action of the cancellation token on that thread, not on the thread your preview is running on.
The gold standard in these cases is to not use two UI threads. This will usually cause trouble and the work you need to handle them is usually not worth it.
If you want to stay with your solution or if you want to trigger cancellation from a background thread, you have to marshal your close operation to the thread your window is opened in:
Action closeAction = () => previewWindow.Close();
previewWindow.Dispatcher.Invoke(closeAction);
The problem with your code is
With the selection of a menu item, a second window is created on a
background thread:
// Print Preview
public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
// Was cancellation already requested?
if (ct.IsCancellationRequested)
ct.ThrowIfCancellationRequested();
...............................
// Use my custom document viewer (the print button is removed).
var previewWindow = new PrintPreview(fixedDocumentSequence);
//Register the cancellation procedure with the cancellation token
ct.Register(() =>
previewWindow.Close()
);
previewWindow.ShowDialog();
}
}
And what I presume to be
Task.Run(() => PrintPreview(foo, cancel));
The correct solution is to do everything on a single thread.
public static Task<bool> PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
var tcs = new TaskCompletionSource<bool>();
// Was cancellation already requested?
if (ct.IsCancellationRequested)
tcs.SetResult(false);
else
{
// Use my custom document viewer (the print button is removed).
var previewWindow = new PrintPreview(fixedDocumentSequence);
//Register the cancellation procedure with the cancellation token
ct.Register(() => previewWindow.Close());
previewWindow.Closed += (o, e) =>
{
var result = previewWindow.DialogResult;
if (result.HasValue)
tcs.SetResult(result.Value);
else
tcs.SetResult(false);
}
previewWindow.Show();
}
return tcs.Task;
}
Then call
var shouldPrint = await PrintPreview(foo, cancel);
if (shouldPrint)
await PrintAsync(foo);

How to close a form (used from a background thread) in thread safe manner?

I have a form that shows a data grid. I also have a method running on a different thread that updates only the displayed cells of the grid. To do this, this method calls a function on the form that returns the displayed cells.
The problem I have is that sometimes while the form has been closed and disposed the method on the other thread is still calling this function which results in an objectdisposed exception. Is there a way (other then making sure the methode on the other thread is finished) to prevent this?
So I need a thread safe method to kill the background task when the form is closed.
private delegate List<foo> GetShownCellsDelegate();
public List<foo> GetShownCells()
{
if (this.InvokeRequired)
{
GetShownCellsDelegate getShownCellsDelegate = new GetShownCellsDelegate(GetShownCells);
return (List<foo>)this.Invoke(getShownCellsDelegate);
}
else
{
//do stuff
}
}
I tries using the IsDisposed property of the form:
if (!IsDisposed)
{
return (List<foo>)this.Invoke(getShownCellsDelegate);
}
But apparently the form can be dispossed after the if statement because I still get the isdisposed exception.
This is how I use the function on the other thread:
private CancellationTokenSource cts = new CancellationTokenSource();
public void CancelUpdate()
{
cts.Cancel();
}
public void ReadDataFromDevice()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ReadAllDataThreadPoolMethod));
}
private void ReadAllDataThreadPoolMethod(Object stateInfo)
{
if (!cts.IsCancellationRequested)
{
//do stuff
}
}
The CancelUpdate method is called from the IsClosing event on the form. But I still get the isdisposed exception sometimes.
To cancel the long running operation you can use a CancellationToken, which is specifically designed for cooperative cancellation.
Have the main form create a CancellationTokenSource when starting the background thread, pass the CacellationToken generated by the CTS to the backround thread, cancel the CTS when your form closes, and then have the background thread check the token to see if it is cancelled before trying to invoke back to the main thread.
public void Foo()
{
var cts = new CancellationTokenSource();
var task = Task.Run(() => DoWork(cts.Token));
FormClosing += (s, args) =>
{
cts.Cancel();
if (!task.IsCompleted)
{
args.Cancel = true;
task.ContinueWith(t => Close());
}
};
}
private void DoWork(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
//Do some work
}
}
To be absolutely sure that the background thread doesn't pass the cancellation check, then yield to the UI thread to have it cancel the token and dispose of the form, before the work is done, you'll also need to ensure that the background thread has time to run to completion after being cancelled, before the form closes. This can be done through a simple Thread.Join call in the closing handler.
this.FormClosed += new FormClosedEventHandler(form1_FormClosed);
void form1_FormClosed(object sender, FormClosedEventArgs e)
{
//close thread
}
This will be executed whenever your form is being closed.

Displaying progress dialog only if a task did not finish in specified time

I have a little service that uploads an image, and I use it like this:
ImageInfo result = await service.UploadAsync(imagePath);
What I'd like to do is to display a progress dialog, but only if the upload operation takes more than, say, 2 seconds. After the upload is done, I want to close the progress dialog.
I made a crude solution using Task/ContinueWith, but I was hoping for a more "elegant" way.
How can I achieve this using async/await?
May be something like this?
var uploadTask = service.UploadAsync(imagePath);
var delayTask = Task.Delay(1000);//Your delay here
if (await Task.WhenAny(new[] { uploadTask, delayTask }) == delayTask)
{
//Timed out ShowProgress
ShowMyProgress();
await uploadTask;//Wait until upload completes
//Hide progress
HideProgress();
}
Thought I'd still post my solution, as it illustrates how to show and dismiss a modal dialog. It's for WPF, yet the same concept applies to WinForms.
private async void OpenCommand_Executed(object sCommand, ExecutedRoutedEventArgs eCommand)
{
// start the worker task
var workerTask = Task.Run(() => {
// the work takes at least 5s
Thread.Sleep(5000);
});
// start timer task
var timerTask = Task.Delay(2000);
var task = await Task.WhenAny(workerTask, timerTask);
if (task == timerTask)
{
// create the dialog
var dialogWindow = CreateDialog();
// go modal
var modalityTcs = new TaskCompletionSource<bool>();
dialogWindow.Loaded += (s, e) =>
modalityTcs.SetResult(true);
var dialogTask = Dispatcher.InvokeAsync(() =>
dialogWindow.ShowDialog());
await modalityTcs.Task;
// we are now on the modal dialog Dispatcher frame
// the main window UI has been disabled
// now await the worker task
await workerTask;
// worker task completed, close the dialog
dialogWindow.Close();
await dialogTask;
// we're back to the main Dispatcher frame of the UI thread
}
}

Using the cancellationToken without Task.Wait()

I have a winforms app with a long running task and two buttons.
One button Start and one Stop.
A new task with a cancellation Token starts when I press the start button.
And if I press the Stop button the cancellation Token's Cancel Method gets called.
I want the UI to be usable during all the time so where do I put the try, catch block for this job. In all the examples I saw they put it around t.Wait();
But if I do that the UI freezes and that is the reason why I used a Task in the first place, to continue using the ui while doing the task. So where to put the try catch block without using Task.Wait.
Start button:
tokenSource2 = new CancellationTokenSource();
ct = tokenSource2.Token;
t = new Task(doStart, ct);
t.Start();
Stop button:
tokenSource2.Cancel();
You could update doStart to handle the cancellation event and exit the task gracefully so you wouldn't need to use Wait at all e.g.
public void doStart(CancellationToken token)
{
while(...)
{
...
if (token.IsCancellationRequested)
break;
}
}
Alternatively, you could wait for the task result on another thread e.g.
Thread.QueueUserWorkItem((state) =>
{
try
{
t.Wait();
}
catch(...)
{
...
}
});

Categories

Resources