I'm playing around with WPF. I am limited to the .Net framework 3.5. I want to update a text box with simple status text while I run some long method. No matter what I try, I cannot seem to get the text box to update until the long method has completed. I have tried threading / using the controls dispatcher etc. In the example below, I have reverted back to simply hiving off the long method to a thread but it still wont work. The TextStatus textbox never gets updated until after the long method (LoadDevices) has completed. Can someone tell me how to do this? Any help much appreciated.
private void UpdateButton_Click(object sender, RoutedEventArgs e)
{
UpdateStatus("Searching for devices, please wait . . .");
var t = new Thread(LoadDevices);
t.Start();
}
private void UpdateStatus(string status)
{
TextStatus.AppendText(status);
TextStatus.InvalidateVisual();
}
I think you are not providing enough code to figure out the problem. Still, fact is that your UI is blocked.
Try the following, maybe it helps you figure it out (not using Task since it's not available in .NET Framework 3.5). It tries to simulate your long running LoadDevices() method while keeping the UI responsive.
MainWindows.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="120"
Width="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Click="UpdateButtonClick" Grid.Row="0">Update</Button>
<TextBox Name="TextStatus" Text="" TextWrapping="Wrap" Grid.Row="1"></TextBox>
</Grid>
</Window>
MainWindows.xaml.cs
using System;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void UpdateButtonClick(object sender, RoutedEventArgs e)
{
UpdateStatus("Searching for devices, please wait");
var thread = new Thread(LoadDevices);
thread.Start();
}
private void LoadDevices()
{
// Your long running "load devices" implementation goes here
for (int i = 0; i < 15; i++)
{
Dispatcher.BeginInvoke((Action) (() => UpdateStatus(".")));
Thread.Sleep(250);
}
Dispatcher.BeginInvoke((Action)(() => UpdateStatus(" done")));
}
private void UpdateStatus(string status)
{
TextStatus.AppendText(status);
}
}
}
But yeah, you should prefer MVVM, Data binding, Commands, etc. and try to avoid stuffing logic into codebehind.
If you haven't tried delegates, that may be what you are looking for although it seems you may of tried this already. Inside your LoadDevices thread method, you could delegate back to invoke UpdateStatus with whatever text you want while your long method is running.
The other case I see with the wording of your question is something local to the method Update-status is trying to change the text by a call to it. However, it cannot for some reason.
This may be compleatly irrelevant to WPF, but in Forms:
I'm assuming you have your loading thread somewhere else. I don't see you calling a doWork() or equivalent method in the thread though. If you want to update the status as your loading thread loads devices you could do:
private delegate void UpdateStatusDel(string text); //This at your declarations
UpdateStatusHandler = new UpdateStatusDel(UpdateStatus); //To initialize the delegate to / //point to your update textbox function
//say you have
string updateText = "Loading 10% done";
//Then, in your thread you could invoke
[locationOfHandeler].Invoke(UpdateStatusHandler, new object[] { updateText });
Try using the Task library. You will need to download this for .NET 3.5 : http://www.microsoft.com/en-us/download/details.aspx?id=24940
Task task = new Task(new Action(LoadDevices));
task.Start();
There are several ways to do this:
http://dotnetcodr.com/2014/01/01/5-ways-to-start-a-task-in-net-c/
Related
I have been deploying updates for an application of mine with ClickOnce for a while. While I'm happy to be able to make improvements, I'm a little frustrated with the current progress bar. A little background - I have a XAML window class called "UpdateProgress" that I open when an update is being undertaken for the application. Here's the current code snippet I'm using right now, which does at least notify the user that progress is being made without freezing the application/crashing, but DOES NOT visually update the progress bar:
case UpdateStatuses.UpdateAvailable:
DialogResult dialogResult = System.Windows.Forms.MessageBox.Show("An update is available. Would you like to update the application now?", "Update available", MessageBoxButtons.OKCancel);
if (dialogResult.ToString() == "OK")
{
BackgroundWorker bgUpdate = new BackgroundWorker();
UpdateProgress updateNotify = new UpdateProgress();
bgUpdate.WorkerReportsProgress = true;
bgUpdate.DoWork += (uptSender, uptE) => { UpdateApplication();};
bgUpdate.ProgressChanged += (progSender, progE) => { updateNotify.updateProgress.Value = progE.ProgressPercentage; };
bgUpdate.RunWorkerCompleted += (comSender, comE) => {
updateNotify.Close();
applicationUpdated();
};
updateNotify.Show();
bgUpdate.RunWorkerAsync();
}
break;
Basically, I'm creating a background worker above, which runs the code below:
private static void UpdateApplication()
{
try
{
ApplicationDeployment updateCheck = ApplicationDeployment.CurrentDeployment;
//BackgroundWorker bgWorker = new BackgroundWorker();
//UpdateProgress updateNotify = new UpdateProgress();
//updateCheck.UpdateProgressChanged += (s, e) =>
//{
// updateNotify.updateProgress.Value = e.ProgressPercentage;
//};
//bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(UpdateComponent.noteUpdates);
//bgWorker.RunWorkerAsync();
//updateCheck.UpdateCompleted += (s, e) =>
//{
// updateNotify.Close();
// applicationUpdated();
//};
//updateNotify.Dispatcher.InvokeAsync(() =>
// {
//updateNotify.Show();
updateCheck.Update();
//});
//return null;
}
catch (DeploymentDownloadException dde)
{
System.Windows.MessageBox.Show("Cannot install the latest version of the application. Please check your network connection, or try again later. Error: " + dde);
//return null;
}
}
Quick explanation, currently I'm only creating an "ApplicationDeployment" instance called "updateCheck" and just having it run the update in this thread. What I've tried attempting before, is loading some of the commented code below, only to see the application crash when updating. Turns out, when debugging with a PROD instance of my application, it's due to the following error:
The calling thread cannot access this object because a different thread owns it.
Now, doing some digging, I've seen quite a few good reads about this. From what I understand, part of the problem is that I'm trying to run this code from a static class separated from my MainWindow and other UI classes. I'm doing this to try to keep my code clean and modular, but apparently, that comes with a price. I realize that one can bind the progress bar's progress percentage if it's in the code-behind of, say, the progress bar's window, but what if I'm trying to stick to running this in the static class I speak of instead? I've tried using things like the Dispatcher methods/BeginInvoke(), but unfortunately to end up with the same result.
Can someone give me the best suggestion on how to update the progress of my progress bar in a window with the percentage progress of an ApplicationDeployment instance's update routine?
Thanks a super ton in advance.
You're mis understanding the cause of your error. Any UI control should be updated from the thread that owns it.
First what you need to wrap is only the line of code that updates your progress bar.
Then you have two ways to wrap your call, either using IProgress<T> or Dispatcher. The former being quite cool as basically you're invoking an Action<T> and Progress<T> ensures to run it in the synchronization context it was instantiated, e.g. the UI thread. This is nice as basically you're abstracting things VS directly using the WPF Dispatcher.
Two really different approaches here, first is declared at caller then callee calls its Report method, second effectively wraps the call to UI in callee.
That's what you are executing during bgUpdate.ProgressChanged that needs to be taken care of.
And now if I were you I'd ditch BackgroundWorker in favor of Task since it's the preferred way to do that now, especially in WPF.
Smallest example using Task and IProgress:
Code:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<Button Content="DoWork" Click="Button1_Click" />
<ProgressBar Height="20" x:Name="ProgressBar1" Maximum="1.0"/>
</StackPanel>
</Window>
Code:
using System;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private async void Button1_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>(s => { ProgressBar1.Value = s; });
await Task.Run(() => DoWork(progress));
}
private static async Task DoWork(IProgress<double> progress = null)
{
const int count = 100;
for (var i = 0; i < count; i++)
{
await Task.Delay(50);
progress?.Report(1.0d / (count - 1) * i);
}
}
}
}
Now you're code doesn't even need to know about Dispatcher that is WPF-specific, code could be anywhere, update any framework.
You could also cancel the operation with Task.Run:
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netframework-4.7.2
Let's say I have a very simple ProgressBar with IsIndeterminate=true:
<Window x:Class="My.Controls.IndeterminateProgressDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="Window"
Width="300" Height="110" ResizeMode="NoResize" Topmost="True"
WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid>
<ProgressBar Width="200" Height="20" IsIndeterminate="True" />
</Grid>
</Window>
I want to show this dialog during a Task that may take a while. I don't care about progress (I can't determine it), I just want to inform the user that I do something that may take some seconds.
public void GetResult()
{
string result = DoWhileShowingDialogAsync().Result;
//...
}
private async Task<string> DoWhileShowingDialogAsync()
{
var pd = new IndeterminateProgressDialog();
pd.Show();
string ret = await Task.Run(() => DoSomethingComplex()));
pd.Close();
return ret;
}
However the UI just freezes infinitely and the Task seems to never return. The problem doesn't lie within DoSomethingComplex(), it completes without issues if I run it synchronously. I'm pretty sure it's because I misunderstood something with await/async, can someone point me in the right direction?
.Result
That's a classic UI thread deadlock. Use await. Use it all the way up the call tree.
Just for some clarification, 'use it all the way up the call tree' means you need to call it from the UI thread. Something like this:
private Task<string> DoWhileShowingDialogAsync()
{
return Task.Run(() => DoSomethingComplex());
}
private string DoSomethingComplex()
{
// wait a noticeable time
for (int i = 0; i != 1000000000; ++i)
; // do nothing, just wait
}
private async void GetResult()
{
pd.Show();
string result = await DoWhileShowingDialogAsync();
pd.Close();
}
I created a BackgroundWorker (mainBw) in my UI (WPF) thread. It has an infinite loop where it sleeps for 1.5 sec and calls a function via Application.Current.Dispatcher.Invoke which just outputs text from the "global" text variable to a TextBox.
Also before the loop it created another (child) BackgroundWorker which ReportsProgress, in ProgressChanged event handler it modifies the text variable.
I thought that it will not work because there is no anything like WinForms Application.DoEvents() in the mainBw loop so it can't process the event handler. But it works. Why?
Here is the code:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WpfApplication6
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private BackgroundWorker mainBw = new BackgroundWorker();
private void Button_Click(object sender, RoutedEventArgs e)
{
mainBw.DoWork += MainBwOnDoWork;
mainBw.RunWorkerAsync();
btn.IsEnabled = false;
}
private string text = "abc";
private void MainBwOnDoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += BwOnDoWork;
bw.ProgressChanged += BwOnProgressChanged;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
while (true)
{
Thread.Sleep(1500);
text += " main ";
Application.Current.Dispatcher.Invoke(new Action(() => { WriteToUIThread(); }));
}
}
private void WriteToUIThread()
{
tbox.Text = DateTime.Now + " " + text + Environment.NewLine + tbox.Text;
}
private void BwOnProgressChanged(object sender, ProgressChangedEventArgs e)
{
text += e.UserState.ToString();
}
private void BwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
while (true)
{
Thread.Sleep(3000);
(sender as BackgroundWorker).ReportProgress(0, "child");
}
}
}
}
// XAML
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Name="btn" Content="Button" HorizontalAlignment="Left" Height="105" Margin="43,47,0,0" VerticalAlignment="Top" Width="165" Click="Button_Click"/>
<TextBox Name="tbox" HorizontalAlignment="Left" Height="114" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="456" Margin="27,182,0,0"/>
</Grid>
</Window>
BackgroundWorker uses a universal way to get code to run on the UI thread, it uses the static SynchronizationContext.Current property to find a synchronization provider. ReportProgress() uses its Post() method to marshal the call.
If you run a Winforms app then the Current property will reference an instance of WindowsFormsSynchronizationContext class. Automatically installed when you create a Form or call Application.Run(). It uses Control.Begin/Invoke() to implement the Post and Send methods.
And if you run a WPF app then the Current property will reference an instance of DispatcherSynchronizationContext, it uses Dispatcher.Begin/Invoke().
So this just works automagically.
It works because the BackgroundWorker does work in a background thread (hence the name). Since it's not running in the UI thread, it's not blocking the UI thread, it is just sending short methods to be run in the UI thread every once in a while.
That said, it's still not a particularly well designed approach to solving the problem. If you want to run some code every 3 seconds just use a Timer instead. If you use the timer in the Forms namespace it will fire it's event in the UI thread on your behalf.
It works, because the BackgroundWorker runs - as its name says - in a background thread independently fromo the main UI thread.
The ReportProgress-event gets marshalled to the UI thread so that this event can easily be handled, without calling Invoke-methods on the controls involved.
Contraray, the Application.DoEvents()-method in WinForms allowed the process to handle other messages for perfoming long going operations in main thread, without using a background thread.
We can achieve the responsive UI using the BackgroundWorker. Here is the example.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Thread test = new Thread(new ThreadStart(TestThread));
test.Start();
}
private void TestThread()
{
for (int i = 0; i <= 1000000000; i++)
{
Thread.Sleep(1000);
Dispatcher.Invoke(
new UpdateTextCallback(this.UpdateText),
new object[] { i.ToString() }
);
}
}
private void UpdateText(string message)
{
Tests.Add(message);
}
public delegate void UpdateTextCallback(string message);
private ObservableCollection<string> tests = new ObservableCollection<string>();
public ObservableCollection<string> Tests
{
get { return tests; }
set { tests = value; }
}
UI:
<StackPanel>
<Button Content="Start Animation" Click="Button_Click" />
<Button Content="Start work" Click="Button_Click_1" />
<Rectangle Name="rec1" Fill="Red" Height="50" Width="50"/>
<ListView ItemsSource="{Binding Tests}" ScrollViewer.CanContentScroll="True" />
</StackPanel>
Here i can start the animation and at the same time i can update the UI using the BackGroundWorker and Dispatcher.
The same thing i can achieve through asynchrony like this:
private async void GetTests(ObservableCollection<string> items)
{
for (int i = 0; i < 20; i++)
{
var s = await GetTestAsync(i);
items.Add(s);
}
}
async Task<string> GetTestAsync(int i)
{
await Task.Delay(2000);
return i.ToString() + " Call - " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();
}
Q.1 is there any advantage of using Asynchrony over BackgroundWorker?
And what are the scenarios where i should decide to use any one of them and should avoid using the other.
Q.2 I want to understand the use of Async - Await in terms of WPF. Like what we can't do without it and now we can do it easily with it.
Please guide.
Q.1 is there any advantage of using Asynchrony over BackgroundWorker?
Sure - primarily, the code is much, much simpler. In this particular case you aren't doing anything complicated, but as soon as you start coordinating multiple tasks, having more complex control flow (loops etc), wanting to handle errors etc, it makes an enormous difference.
Additionally, in this case, it saves an extra thread - in your second call, everything is happening on the UI thread. In more realistic examples, you'd be likely to be waiting for web service calls etc, which definitely don't need to be tying up extra threads.
Q.2 I want to understand the use of Async - Await in terms of WPF. Like what we can't do without it and now we can do it easily with it.
In theory, everything you can do with async you could do before without it. In practice, the amount of boiler plate an incidental complexity in writing asynchronous code correctly and efficiently was an enormous barrier.
The async/await feature lets you write asynchronous code which looks like synchronous code, with familiar control flow etc. It means we can understand it, reason about it, and only worry about the inherent complexity that the asynchrony brings, instead of all the incidental complexity of writing callbacks etc.
The desired scenario:
When I click on the button, I want it to be hidden until async call is completed.
I have a button in xaml like this
<Button Name="btnLoadNextTransactions" Content="Button" Click="btnLoadNextTransactions_Click" Visibility="{Binding LoadMore, Converter={StaticResource converter}}" />
and a click event to
private void btnLoadNextTransactions_Click(object sender, RoutedEventArgs e)
{
App.ViewModel.LoadMore = false;
ApplicationBl<Transaction>.GetDataLoadingCompleted += GetDataLoadingCompleted;
ApplicationBl<Transaction>.GetData(++offset*10, 10);//works only if I comment out this line
App.ViewModel.LoadMore = true;
}
This only works if I comment out async call
//ApplicationBl<Transaction>.GetData(++offset*10, 10);
But that's not a feature I want to comment out :)
I know I'm missing some delagete or dispatcher. I just started coding with SL.
You need to put LoadMore = true in the GetDataLoadingCompleted method.
What's happening is that the line
ApplicationBl<Transaction>.GetData(++offset*10, 10);
Isn't blocking the dispatch thread, so the LoadMore=true get's called right away. The easiest way to do it would probably be with a delegate that you call after getting the data.
So you would change your GetData method to look like this:
public void GetData( int offset, int pageSize, Action callback)
{
//Existing code.
//Notify the callback that we are done.
callback();
}
Once that done just call the method like so:
ApplicationBl<Transaction>.GetData(++offset*10, 10, () =>
{
Deployment.Current.Dispatcher.BeginInvoke(() => App.ViewModel.LoadMore = true;);
});
The reason why you'll need to use the Dispatcher is that the callback is being executed in a background thread, and since the LoadMore property is effecting Gui Elements it needs to be done on the UI thread.