Form showing only after async tasks finished - c#

I've got this custom Task code:
public static async Task Run(this CustomForm parent, Action action)
{
parent.Enabled = false;
using (Caricamento form = new Caricamento())
{
form.TopLevel = true;
form.TopMost = true;
form.Show();
await Task.Run(action);
}
parent.Enabled = true;
}
The gif animation and the text inside the form just won't properly load until the async task finished .
ListMessaggi listForm = new ListMessaggi(ListMessaggi.Tipo.Entrata);
listForm.FormClosing += (o, args) =>
{
if (this.Controls.Count == 2)
{
args.Cancel = true;
}
};
listForm.FormBorderStyle = FormBorderStyle.None;
listForm.Dock = DockStyle.Fill;
listForm.TopLevel = false;
panel.Controls.Add(listForm);
listForm.Show();
And then, in the form which shows up upon listForm.Show() method call I've got:
Finally, this the result showing while the async task is running:
How can I improve the code to make things work properly?

Based on the additional information you provided in the comments, I think that you should try to convert your code to entirely use async / await. This will involve converting all your ADO.NET functions to the new async methods added to ADO in .NET 4.5. This will should eliminate all the Task.Run calls, as well as the messy InvokeRequired and BeginInvoke calls that you are doing to marshal control back to the UI thread.
I think you will find that, if properly implemented, you won't even need the special Run extension method; you will be able to have all your code in-line, just as in "traditional" .net development.
For example, you could use code like this to responsively load data, without locking the UI, in a form's Load event.
public async void Form1_Load(object sender, EventArgs e)
{
var data = await _dataProvider.GetSomeDataFromTheDatabase(aTotallyMadeUpVariable);
this.MyDataGrid.DataSource = data;
}
The exact same pattern works for the event handlers on comboboxes and buttons.
As a side note, event handlers in WinForms is practically the only place that async void methods are legitimately valid. Everything else, including async methods called from within event handler, should be Task returning functions.
As a bit more of a "primer" on async / await, this is how it avoids blocking the UI thread in my example above.
The async modifier on a function acts as a marker to the compiler to convert the method in to a state-machine. The compile will segment the code in the method, breaking it up at each await call, in to separate states. When the function is called, the first state is run (up to where the await is), and then the function returns to the caller. When the function that is being awaitted returns, the code after it (in the next state) is invoked as a continuation. Any data that is shared between the states, such as local variables, is moved off to an object that is passed in to each state's continuation.

Related

COMException: Cannot change thread mode after it is set on EnsureCoreWebView2Async(null) C#

I am writing a C# application that will be loading a technical chart webpage that is generated by an outside server API. I am just trying to display a webpage that i would like to be loaded in a custom panel using WebView2 (as original WebBrowser is deprecated, and Cef has x64 problems).
However, the following code fails to execute properly and is throwing a com exception, when attempting to initialize the custom view:
class ChartView : Panel
{
#nullable enable
private string? chartViewFileAddr { get; set; }
#nullable disable
private WebView2 chartWebView { get; set; }
private async void ensureWeb2Init() => await chartWebView.EnsureCoreWebView2Async(null);
private bool isInit = false;
public ChartView(string? chartViewFileAddr) : base()
{
this.chartViewFileAddr= chartViewFileAddr ?? "";
this.BorderStyle = BorderStyle.Fixed3D;
this.Size = new System.Drawing.Size(300, 500);
this.Location = new Point(0, 320);
this.Name = "ChartWebView";
chartWebView= new WebView2();
chartWebView.Size = this.Size;
chartWebView.Location = new System.Drawing.Point(0, 0);
if (isInit == false)
{
Task.Run(() => this.ensureWeb2Init()).Wait();
isInit = true;
}
chartWebView.CoreWebView2.Navigate("google.com"); //base address for testing
this.Controls.Add(chartWebView);
}
}
When it runs, it fails on calling ensureWeb2Init. If i attempt to run the task without the Wait function, it fails to run asynchronously, if i add the wait function, the async task throws the following:
System.Runtime.InteropServices.COMException: 'Cannot change thread mode after it is set. (0x80010106 (RPC_E_CHANGED_MODE))'
For whatever reason, i cannot get the WebView2 to initialize properly. Is there anything i am doing wrong here? thank you for your help.
The problem: As mentioned in the comments and the other answer, the issue is calling the method in a thread other than the UI thread. According to documentations:
Note that even though this method is asynchronous and returns a Task, it still must be called on the UI thread like most public
functionality of most UI controls.
InvalidOperationException Thrown if this instance of CoreWebView2 is already disposed, or if the calling thread isn't the thread which
created this object (usually the UI thread).
Solutions: You may want to consider either of the following solutions based on your requirements:
Set the Source property, instead of calling EnsureCoreWebView2Async
You can set the WebView2.Source property, which will initialize the CoreWebView2 implicitly if it's not already initialized. Then you don't need to call EnsureCoreWebView2Async. For example:
webView21.Source = new Uri("https://google.com");
Expose an async Initialize method, and await call it when you need
If you need to call the EnsureCoreWebView2Async, you can expose an async method which does the initializations and await call it when you need it. For example:
Define the following method for your control:
public async Task InitializeControl(string url)
{
await webView21.EnsureCoreWebView2Async();
webView21.CoreWebView2.Navigate("https://google.com");
}
Then use it like this, when you want to initialize it:
private async void Form1_Load(object sender, EventArgs e)
{
await myControl.InitializeControl("https://www.google.com");
}
Call EnsureCoreWebView2Async without await, then do rest of initializations in the event handler ofCoreWebView2InitializationCompleted event
You can call the EnsureCoreWebView2Async without await, then you can just do the initialization in the CoreWebView2InitializationCompleted event handler. For example:
webView21.CoreWebView2InitializationCompleted += (obj, args) =>
{
webView21.CoreWebView2.Navigate("https://google.com");
};
webView21.EnsureCoreWebView2Async();
More information and references:
WebView2: A good overview of initialization of the control.
Source: An overview of the Source property.
EnsureCoreWebView2Async: Considerations when calling EnsureCoreWebView2Async method.
The exception means that the code must be run on a UI thread (specifically, the UI thread that created the control). This is common for UI COM components.
The code you posted is using Task.Run to execute the code on a thread pool thread. To run it on the UI thread instead, remove the Task.Run.

How do I marshal an event from Task.Run back to the UI thread?

I have a method that is "partially" async, meaning that one code path runs async and the other runs synchronously. I can't currently make the synchronous part async, although I may be able to in the future.
public async Task UpdateSomethingAsync(){
if (ConditionIsMet){
await DoSomethingAsync;
}else{
DoSomethingSynchronous;
}
}
Both DoSomethingAsync and DoSomethingSynchronous are I/O bound. Calling this method from the Winforms UI thread with "await" causes it to block the UI thread if the Synchronous path is taken, which is to be expected.
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This blocks if the DoSomethingSynchronous path is taken, causing UI to
//become unresponsive.
await UpdateSomethingAsync();
}
So off to Stephen Cleary's blog I go. His suggestion (although for CPU bound code instead of I/O bound) is to run the method with Task.Run, as if it were completely synchronous, while documenting that the method is "partially" async. However, events raised by DoSomethingSynchronous now cause an exception, I believe due to the fact that they are now on a different thread from the UI.
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This no longer blocks, but events will not marshal back to UI Thread
//causing an exception.
await Task.Run(()=> UpdateSomethingAsync());
}
How can this be fixed?
Don't update the UI, or any model bound to the UI inside of UpdateSomethingAsync or any of the methods that it calls. Create a class that will hold the data required to update your UI, and return an instance of that class from UpdateSomethingAsync.
DoSomethingAsync will return a Task<ThatClassYouCreated> and DoSomethingSynchronous just returns an instance of ThatClassYouCreated. Then, back in MyDropDownBox_DropDownClosed after you await UpdateSomethingAsync, use the instance returned by UpdateSomethingAsync to update your UI or your model.
public class UpdatedInformation
{
public int UpdateId { get; set; }
public string UpdatedName { get; set; }
public DateTimeOffset Stamp { get; set; }
// etc, etc...
}
public class YourForm : Form
{
private async Task<UpdatedInformation> DoSomethingAsync()
{
var result = new UpdatedInformation();
// Something is awaited...
// Populate the properties of result.
// Do not modify your UI controls. Do not modify the model bound to those controls.
return result;
}
private UpdatedInformation DoSomethingSynchronous()
{
var result UpdatedInformation();
// Populate the properties of result.
// Do not modify your UI controls. Do not modify the model bound to those controls.
return result;
}
private async Task<UpdatedInformation> UpdateSomethingAsync()
{
if (ConditionIsMet)
{
return await DoSomethingAsync();
}
else
{
return await Task.Run(DoSomethingSynchronous);
}
}
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
var updatedInformation = await UpdateSomethingAsync();
// Now use updatedInformation to update your UI controls, or the model bound to
// your UI controls.
model.Id = updatedInformation.UpdateId;
// etc...
}
}
In your event handler, you can use Invoke() to update the UI like this:
private void someEventHandler() // <- it might have params
{
// ... possibly some other code that does NOT update the UI ...
this.Invoke((MethodInvoker)delegate {
// ... it's safe to update the UI from in here ...
});
// ... possibly some other code that does NOT update the UI ...
}
I don't know who keeps doing it, but my comments below this post keep getting deleted.
This answers the TITLE of the question, which was:
How do I marshal an event from Task.Run back to the UI thread?
When you receive an event from a different thread, this is a perfectly valid way of updating the UI.
Sicne you state that "[..] DoSomethingSynchronous [is] I/O bound" you could also make it async by wrapping the IO bound operation within DoSomethingSynchronous in a Task.Run.
So if DoSomethingSynchronous is something like
public void DoSomethingSynchronous(...)
{
// some UI work
// blocking sysnchornous IO operation
var res = IoOperation();
// some more UI work
}
you could rewrite it to.
public async Task DoSomethingSynchronous(...)
{
// some UI work
// no-UI-Thread blocking IO operation
var res = await Task.Run(() => IoOperation()).ConfigureAwait(true);
// some more UI work
}
the .ConfigureAwait(true) could maybe omited but ensures that the code after the await will be scheduled in the orignal sync-context i.e. the UI-Thread.
You then obviously need to rename the method and such, but this will make the code more maintainable if you someday can use a true asycn IO in DoSomethingSynchronous
Since UpdateSomethingAsync needs to access the UI context, it shouldn't be wrapped in a Task.Run call. (You should very rarely, need to call an async method from Task.Run, usually only if the method is implemented incorrectly and you can't fix it.)
Instead DoSomethingSynchronous should be the thing you call from Task.Run. After all, the purpose of that method is to asynchronously run a synchronous method in a thread pool thread. So only use it for the synchronous method you want run in a thread pool thread, not the (supposedly) asynchronous method that needs to access the UI context.
WinUI 3 respects the below method.
DispatcherQueue.TryEnqueue(() =>
{
//Code to Update the UI
});
Figured I'd answer this myself after some more research. Most of the other answers are correct in some way, but don't necessarily explain the whole deal in one go, so I'll try to sum up here.
This first snippet from the question works event wise, but blocks if the Synchronous path in UpdateSomethingAsync is taken. Events work because "await" automatically captures the SynchronizationContext (this is key) for the UI thread, such that any events raised from UpdateSomethingAsync are marshalled back to the UI, via the SynchronizationContext. This is just the normal way of using async/await:
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This blocks if the DoSomethingSynchronous path is taken, causing UI to
//become unresponsive, but events propagate back to the UI correctly.
await UpdateSomethingAsync();
}
Task.Run works in much the same way, if you aren't using it to run an async method. In other words, this works without blocking and will still send events to the UI thread, because UpdateSomethingAsync is replaced with a Synchronous method. This is just the normal usage of Task.Run:
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//UpdateSomethingAsync is replaced with a Synchronous version, and run with
// Task.Run.
await Task.Run(UpdateSomethingSynchronously());
}
However, the original code in question is Async, so the above doesn't apply. The question poses the following snippet as a possible solution, but it errors out with an Illegal Cross Thread call to the UI when an event is raised, because we are using Task.Run to call an Async method, and for some reason this does not set the SynchronizationContext:
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This no longer blocks, but events raised from UpdateSomethingAsync
//will cause an Illegal Cross Thread Exception to the UI, because the
//SyncrhonizationContext is not correct. Without the SynchronizationContext,
//events are not marshalled back to the UI thread.
await Task.Run(()=> UpdateSomethingAsync());
}
What does seem to work is to use Task.Factory.StartNew to assign the UI SynchronizationContext to the Task using TaskScheduler.FromCurrentSynchronizationContext, like so:
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This doesn't block and will return events to the UI thread sucessfully,
//because we are explicitly telling it the correct SynchronizationContext to use.
await Task.Factory.StartNew(()=> UpdateSomethingAsync(),
System.Threading.CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext);
}
What also works, and is very simple but "lies" a little to the caller, is to simply wrap DoSomethingSynchronous in Task.Run:
public async Task UpdateSomethingAsync(){
if (ConditionIsMet){
await DoSomethingAsync;
}else{
await Task.Run(DoSomethingSynchronous);
}
}
I consider this a little bit of a lie, because the method is not really fully Async in the sense that it spins off a Thread Pool thread, but may never pose an issue to a caller.
Hopefully this makes sense. If any of this is proven incorrect please let me know, but this is what my testing has uncovered.

C# - ' Dispatcher.BeginInvoke(new Action(() => ' doesn't work within Winforms. CS0120 [duplicate]

I am trying to understand better how can I update a windows forms progress bar from an async operation but I am getting some unexpected behavior from that.
Basically I am having a button which should after is being clicked to update a progress bar and then set it back to 0 once the progress bar gets 100% updated.
This is my code:
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync().ContinueWith((prevTask) =>
{
prevTask.Wait();
progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
});
}
private static async Task ExecuteMethodAsync(IProgress<double> progress = null)
{
double percentComplete = 0;
bool done = false;
while (!done)
{
if (progress != null)
{
progress.Report(percentComplete);
}
percentComplete += 10;
if(percentComplete == 100)
{
done = true;
}
}
}
private async Task CallMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, args) => { progressBar1.Increment(10); };
await ExecuteMethodAsync(progress);
}
Having this implementation the progress bar is not being updated at all even if I call "Wait()" on the operation that should update the value of the progress bar.
If i remove this part of code:
progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
the progress bar gets updated but it remains all the time like that, and I want to set it back to 0 once it was entirely filled so that I can update it again when I click again the button.
Could someone please explain me what am I doing wrong ?
One of the reasons async-await syntax was invented because it was difficult to follow the sequence of instructions when tasks were concatenated using functions like ContinueWith.
If you use async-await it is seldom necessary to use statements like ContinueWith. After an await, the thread already continues with the statements after the await.
If the button is clicked, you want to call ExcecuteMethodAsync. This function takes an IProgress, because it wants to report progress regularly. You want to call this function asynchronously, so whenever the function has to wait for something, it doesn't really wait, but returns control to you so you could do other things instead of really waiting, until you encounter an await, in which case your caller continues processing until he encounters an await, etc.
The nice thing with async-await is that the thread that continues after your call to an async function has the same context as the calling thread. This means that you can regard it as your original thread. No InvokeRequired, no need to protect data with mutexes etc.
Your function could be simplified as follows:
async Task CallMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += OnProgressReported;
await ExecuteMethodAsync(progress);
}
private void OnProgressReported(object sender, ...)
{
// because this thread has the context of the main thread no InvokeRequired!
this.progressBar1.Increment(...);
}
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync();
}
So when the button is clicked, CallMethodAsync is called. This function will create A Progress object and subscribes on its Report event. Note that this is still your UI-thread. Then it calls ExecuteMethodAsync, which will regularly raise event Report, which is handled by OnProgressReported.
Because ExecuteMethodAsync is async, you can be sure there is somewhere an await in it. This means that whenever it has to await, control returns to the caller, which is CallMethodAsync, until is encounters an await, which in this case is immediately.
Control goes up the call stack to the caller, which is button1_click, where it immediately encounters an await, so control goes up the call stack, etc.
All these controls have the same context: it is as if they are the same thread.
An article that helped me a lot to understand async-await is this interview with Eric Lippert. Search somewhere in the middle for async await
Another articel that helped me a lot to learn good practices were this article by the ever so helpful Stephen Cleary and Async/Await - Best Practices in Asynchronous Programming also by Stephen Cleary
Your issue is happening because ExecuteMethodAsync(...) is not actually asynchronous.
Add the following before the while loop to make it asynchronous
await Task.Delay(1);
or enclose some synchronous portion of code (e.g. the while loop) into a:
await Task.Run(() => { ... });
or (the best one), add the following at the beginning of the function:
await Task.Yield(); // Make us async right away

How to invoke multiple controls in a single thread?

I am fetching data from a database and binding it to 3 different combo boxes simultaneously based on the parent comboBox value. Following is the example, I have three combo boxes named
i. comboBoxBranch
ii. comboBoxClass
iii. comboBoxSection
Value of comboBoxClass is fetched from database on the basis of selected branch, Similarly the value of comboBoxSection is fetched on the basis of selected branch and selected class. So the order of binding is (ComboBoxBranch) then comboBoxClass and then comboBoxSection.
Now in order to acheive this I am using seperate thread to call GetBranches() method to bind data with comboboxBranch in following way.
private void GetBranches() (This is working perfectly fine)
{
if (comboBoxBranches.InvokeRequired)
{
comboBoxBranches.BeginInvoke(((MethodInvoker) delegate
{
comboBoxBranches.DataSource = _schoolManagementSystemServiceClient.GetBranches();
comboBoxBranches.ValueMember = "BranchId";
comboBoxBranches.DisplayMember = "BranchName";
}));
}
Now the problem occurs how should I bind data with other two comboBoxes that are comboxClass and comboBoxSection, Should I use another thread to as I am using for Getbranch Method or there is any other clean method to achieve this. Following is my GetClasses method that I am calling in comboBoxBranches_SelectedValueChanged() event method of comboBoxBranches.
private void comboBoxBranches_SelectedValueChanged(object sender, EventArgs e)
{
Thread thread=new Thread(GetClasses());
thread.start();
}
private void GetClasses()// in this method how should I achieve invoking for multiple controls? What should I do here?
{
if (InvokeRequired)
{
comboBoxBranches.BeginInvoke(((MethodInvoker) delegate
{
Branch branch = comboBoxBranches.SelectedItem as Branch;
}));
comboBoxClasses.BeginInvoke((MethodInvoker) delegate
{
comboBoxClasses.DataSource = _schoolManagementSystemServiceClient.GetClasses(branch.BranchId);
comboBoxClasses.ValueMember = "ClassId";
comboBoxClasses.DisplayMember = "ClassName";
});
}
}
Same method is for comboxBoxSections whose value is based on both ComboBoxBranches and comboBoxClasses? I am new to multi-threading.
Invoking means waiting until the UI thread is idle, then switch to the UI thread and perform some actions. Therefore, the long running task (e.g. querying data from a database) must be performed before invoking takes place.
Today the preferred way to achieve this is to use async/await.
private async void comboBoxBranches_SelectedValueChanged(object sender, EventArgs e)
{
// We are in the UI thread and can access the controls directly.
Branch branch = comboBoxBranches.SelectedItem as Branch;
var classes = await Task.Run(
// This runs in a new thread. At this point the UI is not blocked.
() => _schoolManagementSystemServiceClient.GetClasses(branch.BranchId)
);
// Here the thread joins the UI thread and returns the classes.
// We are in the UI thread again. No need for Invoke.
comboBoxClasses.DataSource = classes;
comboBoxClasses.ValueMember = "ClassId";
comboBoxClasses.DisplayMember = "ClassName";
}
Note the keyword async in the method header. It tells C# to handle this method in a special way. Behind the scenes C# rewrites this method completely to make the magic happen and hides the complexity involved.
To understand how this works, you can imagine that C# puts the lines after the awaited task (the 3 lines with comboBoxClasses) into a callback method.
As explained in Async in depth (Microsoft) you also should rewrite GetClasses to work asynchronously and to return a Task<T> object, instead of starting a new thread here.
var classes = await _schoolManagementSystemServiceClient.GetClassesAsync(branch.BranchId);
See: Asynchronous programming (Microsoft).

How to postpone execution until some event happens?

I have a WebBrowser control and it has InvokeScript method, which you should call only after WebBrowser is loaded.
So I've tried something like this:
private readonly ManualResetEventSlim browserLoaded = new ManualResetEventSlim(false);
private void BrowserLoaded(object sender, NavigationEventArgs navigationEventArgs)
{
browserLoaded.Set();
}
private async Task<object> InvokeScript(string invoke, object[] parameters = null)
{
return await Task.Factory
.StartNew(() =>
{
if (!browserLoaded.Wait(TimeSpan.FromSeconds(10)))
{
throw new Exception("Timeout for waiting browser to load.");
}
})
.ContinueWith(task => parameters == null
? browser.InvokeScript(invoke)
: browser.InvokeScript(invoke, parameters), TaskScheduler.FromCurrentSynchronizationContext());
}
It does not look very nice to me, but works ok when called asynchronously.
Problem appears, when I try to read result value synchronously - app just hangs:
private string GetEnteredText()
{
return (string)InvokeScript("getEnteredText").Result;
}
I know, that I should go all the way async, but I'm wondering what to do with properties:
public override string UserText
{
get
{
return GetEnteredText();
}
set
{
SetEnteredText(value);
}
}
Or async is wrong way to go in this case at all?
Update
Property is a 'glue' between input field value in browser's page and view model in WPF, so I don't see a good way to make it as separate methods, especially because it is a part of the bigger framework (notice override keyword on it).
Once browser control is loaded, execute logic should not take long, I guess less than 10 milliseconds, that is why I would be ok with sync execution in this case. And usually browser control loads fast enough, the only reason here to delay is to make sure InvokeScript is not called before load, not because it taking long time or smth.
app just hangs
We'll, you said it yourself. You know why that happens. You're blocking on the call using Task.Result, that is what causes the deadlock.
Or async is wrong way to go in this case at all?
We don't have async properties. The reason we don't have them are because properties aren't asynchronous by nature. Stephan Cleary describes it nicely:
If your “property” needs to be asynchronously evaluated every time
it’s accessed, then you’re really talking about an asynchronous
operation.The best solution is to change the property to an async
method. Semantically, it shouldn’t be a property.
Instead of a property, make it an asynchronous method which you can await properly.
In regards to using a ManualResetEvent, I would use a TaskCompletionSource<bool> instead. It works "nicer" with TPL:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
private void BrowserLoaded(object sender, NavigationEventArgs navigationEventArgs)
{
tcs.TrySetResult(true);
}
private async Task<object> InvokeScript(string invoke, object[] parameters = null)
{
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10));
if (timeoutTask == await Task.WhenAny(tcs.Task, timeoutTask))
{
// You've timed out;
}
return Task.Run(() =>
{
parameters == null ? browser.InvokeScript(invoke)
: browser.InvokeScript(invoke, parameters)
});
}
I also see that you used TaskScheduler.FromCurrentSynchronizationContext() in your continuation. If you need this to execute on the UI thread, there is no point in using a threadpool thread at all.
#Noseratio adds that WebBrowser is an STA object. His answer here might help.

Categories

Resources