UI is unresponsive in winforms when using async methods - c#

So I have a few methods that I want to call when my form loads (ideally in the constructor but since async/await doesn't work in the constructor I am using the Form_Load event). Originally I was using a separate thread to do this work which was working great. Everything was getting done and the UI was responsive while the work was being done. However, I have read that using async/await is "better", "less resource intensive" and is just generally preferred over creating separate threads. I guess the reasoning is that using async/await uses fewer threads?
But when I use this method as illustrated below, the UI is frozen/unresponsive while the function that takes a few seconds is running.
In my Form_Load event I am calling a synchronous method:
private void Form_Load(object sender, EventArgs e)
{
CheckForDriver();
}
And then here is my CheckForDriver function:
private void CheckForDriver()
{
System.Management.SelectQuery query = new SelectQuery("Win32_SystemDriver") {
Condition = "Description = 'my driver'" };
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection drivers = searcher.Get();
if (drivers.Count > 0) // valid driver, go to next page
{
wizardControl.SelectedTab = startPage;
task = QueryDeviceAsync(false, new List<Button>());
}
}
where task is a field defined as private Task task;
And here is the QueryDeviceAsync function, the part that takes some time is the switcher.GetDeviceAndSize() function.
private async Task QueryDeviceAsync(bool enableForm, List<Button> buttons)
{
lastBackEnable = backBtn.Enabled;
lastNextEnable = nextBtn.Enabled;
EnableButtons(false, false);
this.Enabled = enableForm;
if (buttons != null)
{
foreach (Button button in buttons)
{
button.Enabled = false;
}
}
await Task.Run(() => switcher.GetDeviceAndSize()); // this function takes a few seconds and this is where the UI becomes unresponsive.
ThreadFinished?.Invoke(buttons);
}
and then in the ThreadFinished event handler, I am doing await task; to wait for the QueryDeviceAsync function to finish, at which time I update some UI stuff based on what the switcher.GetDeviceAndSize function did. I was also confused about whether I can/should update UI stuff in an async method, such as when I am disabling the buttons in the buttons list in the QueryDeviceAsync function. I know this doesn't work in a second thread and has to be done on the thread that they were created in, but this runs without issues.
My main problem is that the form is still unresponsive while I'm using these async functions. It works fine when I use a separate thread so I'm inclined to just go back to that but I thought I would try to figure this method out.

In this case you need to offload the blocking synchronous work to a worker thread. for example:
var search = new ManagementObjectSearcher(Query.ToString());
await Task.Run(() => search.Get());

Related

keep windows.forms controls in same Main thread

So, I have a win form that calls a method
dvm.SetVoltage(excitationVoltage, rigNo);
which runs a task in another class
public void SetVoltage(double voltage, int rigNum)
{
Task.Run(() => ReadDVMWorker());
}
Once the worker is finished (voltage set) it triggers an event In the main Form1.cs
private void dvmVoltageSet(object sender, VoltageEventArgs e)
{
VoltageSet = e.VolSet;
TestLvdtNull();
}
Calling TestLvdtNull method:
private void TestLvdtNull()
{
tbMessage.Location = new Point((int)(x / 2 - 250), 150);
}
As soon as the tbMessage line is reached it causes an exception because it has started another thread other than the one tbMessage was created in, how can I prevent it from starting a new thread and continue using the Main thread please?
I have looked at singlethreadsynchronizationcontext, but couldn't make it compile and I know that you can invoke:
tbMessage.Invoke((Action)delegate
{
tbMessage.Location = new Point((int)(x / 2 - 250), 150);
});
But I have many controls with many attributes changing, there must be a way to keep the UI on the main thread?
All UI controls are created at one thread. That is by design in many UI frameworks. After you finish your task you have to return to the UI thread to access UI controls.
One option mentioned in comments is to use async/await where the part of the method after await keyword is executed on the same thread as was before the async method.
// UI thread
await ReadDVMWorker(); // executed at ThreadPool
// UI thread
If you prefer to stay with Task, you can use ContinueWith method with correct TaskScheduler parameter, which ensures that you're back to UI thread. Eg. TaskScheduler.FromCurrentSynchronizationContext()
Async/await attempt code:
private async void button1_Click(object sender, EventArgs e)
{
// Call the method that runs asynchronously.
string result = await WaitAsynchronouslyAsync();
// Display the result.
textBox1.Text += result;
}
//The following method runs asynchronously.The UI thread is not
//blocked during the delay.You can move or resize the Form1 window
//while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
await dvm.SetVoltage(5, rigNo); //Task.Delay(10000);
return "Finished";
}
You could have a method to update arbitrary controls
private void dvmVoltageSet(object sender, VoltageEventArgs e)
{
VoltageSet = e.VolSet;
TestLvdtNull(tbMessage);
TestLvdtNull(tbMessage2);
}
private void TestLvdtNull(Control control)
{
control.BeginInvoke((MethodInvoker)delegate()
{
control.Location += new Point((int)(x / 2 - 250), 150);
});
}
After trying several different ways to solve the problem, I solved the problem by using SynchronizationContext.
This grabs the SyncronizationContext of the thread:
private SynchronizationContext _synchronizationContext;
SynchronizationContext uiContext = SynchronizationContext.Current;
Then after running my task in another class, where previously I was getting an cross thread call exception, I call the method that wants use the same UI thread:
uiContext.Post(MethodToCallOnTheSameUIThread, "string");
After this I can modify and update my textboxes and other controls!
You can check the thread id by:
int id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Thread: " + id);
With thanks to Mike Peretz and his CodeProject

Form is displaying before async function completes

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.

New thread is running as UI thread

I have a form "DisplayImagesForm" which calls a function loadImages() in the constructor:
public ImageScraperForm(string query, RichTextBox textBox)
{
InitializeComponent();
this.query = query;
loadImages();
}
Then the loadImages() function creates a new thread at the end:
{
...
Thread thread = new Thread(readNextImage);
thread.Start();
}
The problem is that the thread doesn't seem to be running as a different thread than UI thread. The readNextImage() method loads images from a server which takes some times - while loading the image it blocks the whole form. It's not normal, because the "thread" should be running separately from the UI thread. Also the readNextImage() function can modify the UI elements without Invoke((MethodInvoker)delegate - no exception is thrown.
If you're trying to download an image over the internet and display it in a WinForm control; you're going about it completely wrong.
Do not do any lengthy processing in your Form constructor; that is going to make your form unresponsive. If you are going to display something in the UI you should do that in your Form's Paint event handler. If you are trying to download something over the internet you should be using await, not a Thread. Threads use a CPU core and, since the internet is orders of magnitude slower than a CPU, you are going to be blocking it immediately.
The correct way of doing this is using await to load the file when you need it. If you need it when you start the Load event handler is a good choice.
private async void YourForm_Load(object sender, EventArgs e)
{
using (var c = new HttpClient())
using (var resp = await c.GetAsync(#"http://uri/to/image.jpg"))
using (var content = resp.Content)
using (var s = await content.ReadAsStreamAsync())
{
_img = new Bitmap(s);
}
YourControl.Invalidate();
}
private void YourForm_Paint(object sender, PaintEventArgs e)
{
if (_img != null)
DrawToYourControl(_img);
}

Asynchronous Thread Lifetime & WPF UI Update

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.

Updating DataGrid from multiple threads

I want to update my a DataGrid from multiple thread in WPF(c#). I use dataGrid.Dispatcher.BeginInvoke() and dataGrid.Dispatcher.Invoke() but they freeze program (main thread). How can update dataGrid from multiple threads with a timeout ( because I use web service that may be unreachable ).
Use a Task kick off the web service request asynchronously. To do this you will probably need to convert the EAP (event-based asynchronous pattern) style into a TAP (task-based asynchronous pattern) style. Here is how you do that.
private Task<IEnumerable<YourDataItem>> CallWebServiceAsync()
{
var tcs = new TaskCompletionSource();
var service = new YourServiceClient();
service.SomeOperationCompleted +=
(sender, args) =>
{
if (args.Error == null)
{
tcs.SetResult(args.Result);
}
else
{
tcs.SetException(args.Error);
}
};
service.SomeOperationAsync();
return tcs.Task;
}
After you have that in place then you can use the new async and await keywords to make the call and wait for it to return using continuation style semantics. It would look like this.
private async void Page_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
IEnumerable<YourDataItem> data = await CallWebServiceAsync();
YourDataGrid.DataSource = data;
}
That is it! It does not get a whole lot more elegant than that. This will perform the operation asynchronously on a background thread and then bind the results to the DataGrid on the UI thread.
If the WCF service is unreachable then it will throw an exception and will be attached to the Task so that it propagates up to the await call. At that point it will be injected into the execution and can be wrapped with a try-catch if necessary.
If you don't need the DataGrid editing to be done in the threads, you can run them in the main thread like this:
this.Invoke((Action)delegate
{
//Edit the DataGrid however you like in here
});
Make sure to only put things you need to be run in the main thread inside it (otherwise that would defeat the purpose of multithreading).

Categories