I have a button in xaml whose mouseclick is connected to a delegate command. In delegate command, I am calling another method which creates bit map and saves it to a pdf. This method takes few seconds(usually more than 10 seconds) during which the app goes to Not responding state where the UI is not accessible. During which time I wanted to show some animation which is already defined in xaml. But showing it just before calling the time-taking-function and hiding it afterwards doesnt work. It shows up at the end of time-taking-function execution if i dont use different thread.
So, I tried using creating a different thread and calling this time taking function. Animation loads fine But the function actually needs to access some UI objects in UI thread. It results in this exception:
"The calling thread cannot access this object because a different thread owns it."
Is there a way to achieve it ? i.e., to show the animation during this function execution ?
Some code snippet:
private void OnExportPDFCommand()
{
PanelLoading = true; // Flag to show animation
Task.Factory.StartNew(() =>
{
CreatePDF(); //time-taking-function
Application.Current.Dispatcher.Invoke(() => PanelLoading = false);
});
}
Please explain with a small example. I am new to WPF. Thanks.
You can make OnExportPDFCommand an async method and await the Task you start:
private async void OnExportPDFCommand()
{
PanelLoading = true; // Flag to show animation
await Task.Run(() =>
{
CreatePDF(); //time-taking-function
});
PanelLoading = false; // stop animation
}
The compiler turns this into a state machine. The control flow is returned to the caller (the UI) at the await keyword. When the Task finishes, execution is eventually resumed at the next statement PanelLoading = false.
This way, the call to PanelLoading is in the UI thread again and no exception should be raised.
If you need to make other calls to the UI (inside CreatePDF) you can use Application.Current.Dispatcher.Invoke(...) to invoke these calls on the UI thread.
As a side note: you might want to disable the button that starts this at the beginning of OnExportPDFCommand() and enable it again at the end. This way you can avoid to start the operation multiple times and get confused with your animation.
Related
I have a button that opens a chatbot in a browser. The problem is that I don't directly have the address for the chatbot. I have to ask a second server for that address, so my button doesn't react immediately to a click.
I'd like it to not open two browser tabs if the button is clicked twice quite quickly.
I need something that looks a bit like Throttle, but instead of checking the time elapsed, checks whether the previous event that it let go by has terminated or not.
I suppose I could do this with a boolean check and a filter in the reactive pipeline, but I don't want to have to deal with thread safety on that boolean since I'm launching the process on a second thread.
For example I have a method that does something long, like calling an api to get an address :
private async Task<string> LongTask()
{
await Task.Delay(1000);
return "a return value";
}
And I'd like to handle it one click at a time, dropping any other clicks that happen before the first event has finished processing a bit like :
var longTaskObservable =
Observable.FromAsync(LongTask) // Create observable from async method that returns one value
.SubscribeOn(NewThreadScheduler.Default); // Run it on a background thread
bool inProgress = false;
Observable.FromEventPattern(btn, "Click") // On a click event
.Where(_ => !inProgress)
.Do(_ => inProgress = true)
.SelectMany(longTaskObservable) // Start the long task observable and use its unwrapped values instead of the event args
.Where(s => !string.IsNullOrEmpty(s)) // Exclude any results that are marked as invalid
.ObserveOnDispatcher() // Set the thread of the subscribe method after to the ui thread
.Subscribe(result => {
LogWithThread("Got result '" + result + "' on thread {0}");
inProgress = false;
}); // Get the result
But that's not very clean or clear or modular and is easy to break.
It seems like it should be so simple to do.. I must be missing something here.
Any help would be much appreciated!
Thank you
There are various ways to do this:
Processing the task synchronous - should only really be done for calls that are very fast. ~50ms
Show a modal progress dialog - This will prevent any interaction with the UI until the operation is complete, but not freeze the UI. Best suited for long operations that can actually show progress, but may be adapted for tasks of varying length.
Disable the button and enable it after the operation is complete - Keep in mind to use try/finally to ensure it is actually re-enabled. And that the application can handle any other button being pressed while the task is processing.
Use a InProgress Flag, as in your example. If you are simply awaiting the task in the button handler, anything after the await will still run on the main thread, so it is perfectly thread safe.
Ensure the button press is Idempotent. Typically the button would set some state, but only run the expensive operation if the state was changed.
I want to update the position of certain UI elements in my WPF application in a loop. After each iteration, the UI should be rerendered to make the changes visible. The updating process can stopped at any point using a CancellationToken. Since the cancellation is performed by the user, the UI must remain responsive to input. I wrote the following method to do this:
public async Task DoStuff(CancellationToken token)
{
do
{
DoLayoutUpdate();
await Dispatcher.Yield(DispatcherPriority.Input);
} while (!token.IsCancellationRequested);
}
This mostly works: The UI is rerendered after each iteration and I can click the button to cancel the operation so input works as well. The problem is: if there is no input and nothing to rerender, the method gets stuck in the Yield. Presumably the thread is blocked waiting for input or render tasks.
If I increase the DispatcherPriority to Render, the method does not get stuck anymore but then the UI isn't updated and input isn't handled anymore.
How can I fix this?
Try await Task.Delay(10); or await Dispatcher.BeginInvoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.Input); instead of Dispatcher.Yield.
This should give the UI thread a chance to render while your loop executes.
If I increase the DispatcherPriority to Render, the method does not get stuck anymore but then the UI isn't updated and input isn't handled anymore.
Actually, the problem is that you changed the priority in the wrong direction. Setting the priority to DispatcherPriority.Background would allow WPF to finish its work and then eventually schedule the continuation to allow the method to resume execution after the await.
I.e.:
public async Task DoStuff(CancellationToken token)
{
do
{
DoLayoutUpdate();
await Dispatcher.Yield(DispatcherPriority.Background);
} while (!token.IsCancellationRequested);
}
Using a higher priority causes your continuation to be scheduled too soon, giving your loop all the dispatcher time, in preference over all the other things WPF needs to do.
Noting, of course, that calling Dispatcher.Yield() without a parameter will default to using DispatcherPriority.Background as well. Either way works fine.
The other ideas suggested in the accepted answer will work too, but they are a bit kludgy as compared to simply yielding with the correct requested continuation priority.
Or the bizarre monstrosity that works no matter what thread you're running on:
await Task.Run(() =>
{
Action action = () => { };
MainWindow.Dispatcher.Invoke(action,
System.Windows.Threading.DispatcherPriority.Background);
});
Dispatcher.Yield() works fine on the UI thread. But it is a static method that operates on Dispatcher.CurrentDispatcher, and there's no equivalent non-static member.
I'm having an issue with a WinRT project. Currently the execution on the program is running on two threads. One thread executes the main application and the other handles the UI side of things. At the moment, I'm having an issue calling a function from the main thread to execute on the UI thread, waiting for a reply and then continuing execution on the main thread... let me show you some code as an example.
public async void SignOut(Action onSuccess, Action onFailure)
{
bool success = false;
bool wait = true;
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await SignOutAsync();
success = true;
wait = false;
});
while (wait) { }
if (success)
{
onSuccess();
}
else
{
onFailure();
}
}
So this code is doing what I want it to do but it's obviously not the right way to go about it with the busy waiting and all of that. The problem is that if I move the OnSuccess/OnFailure execution into the RunAsync lambda then there is an error on the callback about invalid memory because the execution is on a different thread. Currently the problem I'm facing is that I can't remove the busy wait without screwing up the order of execution. Ideally I want to wait for the entire RunAsync lambda to finish execution on the UI thread and then return to the main thread to run the success/fail callbacks.
It appears at the moment that as soon as I hit the await SignOutAsync() part of the RunAsync lambda the RunAsync task marks itself as complete and returns to the Success/Failure check before the SignOutAsync method has any result. I believe this is due to the nested async methods and that you can't really await on the RunAsync call and then again on the async lambda within it.
Any advice would be greatly appreciated.
Currently the execution on the program is running on two threads. One thread executes the main application and the other handles the UI side of things.
This is not ideal. If at all possible, structure your code so that you only have one "special" thread (the UI thread). async allows your UI thread to remain responsive without requiring a second "special" thread.
At the moment, I'm having an issue calling a function from the main thread to execute on the UI thread, waiting for a reply and then continuing execution on the main thread.
Again, a better design is to have your program logic provide "services" to the UI, instead of the other way around. So, do your best to redesign the calls so that the UI is driving the program logic and not the opposite.
That said, if you absolutely must have a single "special" background thread, you can use the AsyncContextThread type from my AsyncEx library. AsyncContextThread understands asynchronous methods, so you can do this:
public async Task SignOutAsync(Action onSuccess, Action onFailure)
{
try
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => SignOutAsync());
onSuccess();
}
catch
{
onFailure();
}
}
However, I would be embarrassed to put this code into production; anything that uses Dispatcher is a code smell. Even though I wrote the AsyncContextThread type, I can't recommend it for Windows Store projects. A far better design is to structure the code so that the program logic never calls back into the UI.
I am working on a Winform Application. The Method is started by a BackgroundWorker Thread. I am sorry. I did not mention this earlier.
private void Method()
{
tasks[i] = Task.Factory
.StartNew(() => fileProcessor.ProcessEachMachine(mdetail))
.ContinueWith(UpdateLabel, TaskContinuationOptions.OnlyOnRanToCompletion);
}
I have a long running function ProcessEachMachine. In the continuation function UpdateLabel I want to access UIlabel and update the status.
private void UpdateLabel()
{
progressLbl.Text = "updated";
}
But the label is not getting updated. How to access UILabel and update the text of it.?
You have to set the TaskScheduler.FromCurrentSynchronizationContext on ContinueWith or else it will not be run in the UI context. Here is the MSDN on the override that you must use for this call to ContinueWith.
It should end up looking like this:
.ContinueWith(UpdateLabel, null,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());
It may seem like nothing is happening, but the TPL is currently swallowing your cross thread exception. You should probably use the UnobservedTaskException if you are not going to inspect each result or check for its exception. Otherwise, when garbage collection occurs, the exception will happen then...which could create hard to debug errors.
UPDATE
Based on your update about the main Task being setup and started by a Backgroundworker, my main question is why this could not use a Task to start? In fact, if there is not more in the Method, then this is really just double work and might confuse other developers. You are already started asynchronously, so why not just do your work within the backgroundworker and use an OnComplete method that will UpdateLabel (as background workers are already context aware).
The main problem is still the same though, so here are some other solutions if you feel you must use the TPL:
You can Invoke back onto the main UI thread within the UpdateLabel method
You can pass the current context into the backgroundworker and use that instead
You can Wait for your original Task to return and then use the worker's oncomplete event to update the label.
Here is how I would do this (all pseudo code)
Background Worker Method:
Method() called because of Background worker
private void Method()
{
fileProcessor.ProcessEachMachine(mdetail);
}
Wire up background worker's OnRunWorkerCompleted:
if(!e.Cancelled && !e.Error)
UpdateLabel();
Task only method
Call Method() from the main thread and just let the TPL do its work :)
Task.Factory.StartNew(() => fileProcessor.ProcessEachMachine(mdetail))
.ContinueWith((precedingTask)=>{if(!precedingTask.Error)UpdateLabel;},
null, TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext());
I've got my main form Form1 running the main bulk of my program.
I have a separate thread started to perform an algorithm.
When I run the method from the new thread, method MyAlgorithm() I get the error
InvalidOperationException with the message, "Control control name accessed from a thread other than the thread it was created on."
How do I get back to the original thread so that I can run the method to update my text boxes with the latest values?
This is the method that I want to run contained in Form1, the main class in my application.
// Reset the results values
public void ShowResults()
{
while (true)
{
loopsNum.Text = Convert.ToString(resultLoopsNum);
nodesVisitedNum.Text = Convert.ToString(resultNodesVisitedNum);
nodesResolvedNum.Text = Convert.ToString(resultNodesResolvedNum);
cpuLoopsNum.Text = Convert.ToString(resultCpuLoopsNum);
shortestPathCostNum.Text = Convert.ToString(resultShortestPathCost);
}
}
I've looked at the Invoke() methods, but I don't know how to get the original instance of my Form1 from the threaded method.
I'm invoking my thread like this...
// Set the algorithm method up in it's own thread
Thread thread = new Thread(new ThreadStart(MyAlgorithm));
// Run the algorithm
thread.Start();
How do I get back to the original thread so that I can run the method to update my text boxes with the latest values?
In Windows Forms, you'd either use Control.Invoke/BeginInvoke or use a BackgroundWorker and perform the update in the progress event handler.
In WPF you'd use Dispatcher.Invoke/BeginInvoke.
In C# 5 and .NET 4.5 you'll be able to use async methods which should make a lot of this much simpler...
I've looked at the Invoke() methods, but I don't know how to get the original instance of my Form1 from the threaded method.
If the "threaded method" is just an instance method of the Form, then you've already got the this reference. If it's not, you'll need to provide that information - ideally as an ISynchronizeInvoke to avoid a direct dependency on Windows Forms if you can express the "update" part separately. (That interface is somewhat deprecated these days, in favour of synchronization contexts, but it still works perfectly well.)
Have a look at Control.Invoke():
public void ShowResults()
{
while (true)
{
Thread.Sleep(1000); // don't spam the UI thread
if (this.InvokeRequired)
{
this.Invoke((Action)UpdateGui);
}
else
{
UpdateGui();
}
}
}
private void UpdateGui()
{
loopsNum.Text = Convert.ToString(resultLoopsNum);
nodesVisitedNum.Text = Convert.ToString(resultNodesVisitedNum);
nodesResolvedNum.Text = Convert.ToString(resultNodesResolvedNum);
cpuLoopsNum.Text = Convert.ToString(resultCpuLoopsNum);
shortestPathCostNum.Text = Convert.ToString(resultShortestPathCost);
}
You can use:
myform.Invoke(ShowResults);
There's other options here too:
Alternately use a System.Forms.Timer to call ShowResults periodically. Or another option would be not to use another thread to do the operation; do it in the GUI thread and call Application.DoEvents() from within the operation when you want to let the GUI update.
The first option is nice because it keeps you from accidentally flooding the GUI with Invoke requests, and the second option is nice because it's all on the GUI thread and allows you to have fine-grain control over when things get displayed on the GUI.