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.
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
I have a WPF application that will launch a long-running task (60+ seconds) that uses a System.Reactive.Subject<string> to push status messages periodically. The idea was that I could then Subscribe to the observable from my ViewModel and have ReactiveUI automatically update my UI through a Data Binding. This all works fine except that the TextBox is not updating in real-time. It only updates after long-running task has completed. I presume this is because my UI thread is being blocked and cannot update.
Working under that assumption, my research suggested that I could put the subscription on a background thread using SubscribeOn and then push the notifications back to the UI thread using ObserveOnDispatcher. However, this still did not produce the results that I wanted -- the UI only updated after long-running task had returned.
Can anybody give me some insight on what I need to do to allow my Output log to update in real time? Below are the related pieces of code.
XAML:
<TextBox Grid.Row="1" Text="{Binding Output}" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="10,0,10,10" x:Name="OutputTextBox" />
Code-Behind:
protected override void OnContentRendered(EventArgs e)
{
if (Converter == null) return;
_viewModel = new ConversionOutputWindowViewModel(Converter);
DataContext = _viewModel;
_viewModel.StartConversion(); // Long-running Task
//_viewModel.StartSave();
FinishButton.IsEnabled = true;
}
ViewModel:
private string _output;
public string Output // Data bound in XAML
{
get { return _output; }
set { this.RaiseAndSetIfChanged(ref _output, value); }
}
public void StartConversion()
{
_edmxConverter.Convert(); // Long-running Task
}
public ConversionOutputWindowViewModel(Utilities.Converters.EdmxConverter converter)
{
_edmxConverter = converter;
_compositeDisposable.Add(_edmxConverter.Output
.SubscribeOn(NewThreadScheduler.Default)
.ObserveOnDispatcher()
.Subscribe(s => Output = Output += s));
//_compositeDisposable.Add(_edmxConverter.Output.Subscribe(s => Output = Output += s));
}
Long-Running Task Function:
public Subject<string> Output { get; }
Output = new Subject<string>(); //In ctor
private void PrintReplacement(XAttribute attribute, string oldValue, string newValue, int level, Verbosity minVerbosity = Verbosity.Informational)
{
if (Verbosity < minVerbosity) return;
Output.OnNext($"{new string('\t', level)}{attribute.Name}: {oldValue} -> {newValue}{Environment.NewLine}");
}
Would it maybe help to wrap my Long-running Task function call inside an await Task.Run? I'm grasping at straws here. I don't have a very good working knowledge of .NET threading.
Well you are using subjects, so .SubscribeOn(NewThreadScheduler.Default) does nothing. (Standard old drum beating) Don't use subjects.
Your long running process should be a method call that returns an IObservable<T> (instead of having a class that exposes a property). The T should be the status updates you want to receive. When the task is done, then you OnComplete. If the task fails, you OnError.
Ideally the method that returns the IObservable using Observable.Create to deifne the work that will need to be done.
public IObservable<string> Convert()
{
return Observable.Create<string>(observer=>
{
//do stuff here.
//Call observer.OnNext with status messages
//When done call observer.OnCompleted()
});
}
As you dont show what your long running task is I cant help anymore with the implementation.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
The code below is causing my WPF application to get hung (likely dead-locks). I have verified that DownloadStringAsTask method is executed on a separate (non-UI) thread. Interestingly if you uncomment the messagebox line (just before call to while (tasks.Any()), application works fine. Can anyone explain why do application hungs at first place and also how to resolve this issue?
<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="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="9*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Frame x:Name="frame" Grid.Row="0" />
<StatusBar VerticalAlignment="Bottom" Grid.Row="1" >
<StatusBarItem>
<TextBlock Name="tbStatusBar" Text="Waiting for getting update" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
public partial class MainWindow : Window
{
List<string> URLsToProcess = new List<string>
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon124.com",
"http://www.msn.com"
};
public MainWindow()
{
InitializeComponent();
ProcessURLs();
}
public void ProcessURLs()
{
var tasks = URLsToProcess.AsParallel().Select(uri => DownloadStringAsTask(new Uri(uri))).ToArray();
//MessageBox.Show("this is doing some magic");
while (tasks.Any())
{
try
{
int index = Task.WaitAny(tasks);
this.tbStatusBar.Text = string.Format("{0} has completed", tasks[index].AsyncState.ToString());
tasks = tasks.Where(t => t != tasks[index]).ToArray();
}
catch (Exception e)
{
foreach (var t in tasks.Where(t => t.Status == TaskStatus.Faulted))
this.tbStatusBar.Text = string.Format("{0} has completed", t.AsyncState.ToString());
tasks = tasks.Where(t => t.Status != TaskStatus.Faulted).ToArray();
}
}
}
private Task<string> DownloadStringAsTask(Uri address)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(address);
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null)
tcs.SetException(args.Error);
else if (args.Cancelled)
tcs.SetCanceled();
else
tcs.SetResult(args.Result);
};
client.DownloadStringAsync(address);
return tcs.Task;
}
}
The biggest problem here is that your constructor does not return until all of the tasks have completed. Until the constructor returns, the window will not be shown, because the window messages related to drawing the window aren't going to be processed.
Note that you don't really have "deadlock" here per se. Instead, if you waited long enough (i.e. until all the tasks have completed), the window would actually be shown.
When you add the call to MessageBox.Show(), you give the UI thread a chance to process the window message queue. That is, the normal modal dialog includes a thread message pump which winds up handling those messages in the queue, including those related to showing your window. Note that even if you add the MessageBox.Show(), that won't result in the window being updated as your processing progresses. It just allows the window to be shown before you block the UI thread again.
One way to address this is to switch to the async/await pattern. For example:
public MainWindow()
{
InitializeComponent();
var _ = ProcessURLs();
}
public async Task ProcessURLs()
{
List<Task<string>> tasks = URLsToProcess.Select(uri => DownloadStringAsTask(new Uri(uri))).ToList();
while (tasks.Count > 0)
{
Task<string> task = await Task.WhenAny(tasks);
string messageText;
if (task.Status == TaskStatus.RanToCompletion)
{
messageText = string.Format("{0} has completed", task.AsyncState);
// TODO: do something with task.Result, i.e. the actual downloaded text
}
else
{
messageText = string.Format("{0} has completed with failure: {1}", task.AsyncState, task.Status);
}
this.tbStatusBar.Text = messageText;
tasks.Remove(task);
}
tbStatusBar.Text = "All tasks completed";
}
I've rewritten the ProcessURLs() method as an async method. This means that when the constructor calls it, it will run synchronously up to the first await statement, at which point it will yield and allow the current thread to continue normally.
When the call to Task.WhenAny() completes (i.e. any of the tasks complete), the runtime will resume execution of the ProcessURLs() method by invoking the continuation on the UI thread. This allows the method to access the UI objects (e.g. this.tbStatusBar.Text) normally, while occupying the UI thread only long enough to process the completion.
When the loop returns to the top and the Task.WhenAny() method is called again, the whole sequence is repeated (i.e. just the way a loop is supposed to work :) ).
Some other notes:
The var _ = bit in the constructor is there to suppress the compiler warning that would otherwise occur when the Task return value is ignored.
IMHO, it would be better to not initialize these operations in the constructor. The constructor is just generally a bad place to be doing significant work like this. Instead, I would (for example) override the OnActivated() method, making it async so you can use the await statement with the call to ProcessURLs() (i.e. a more idiomatic way to call an async method). This ensures the window is completely initialized and shown before you start doing any other processing.
In this particular example, starting the processing in the constructor is probably not really going to hurt anything, as long as you're using async/await, since the UI-related stuff isn't going to be able to be executed in any case until at least the constructor has returned. I just try to avoid doing this sort of thing in the constructor as a general rule.
I also modified the general handling of your task collection, to something that I feel is somewhat more suitable. It gets rid of the repeated reinitialization of the tasks collection, as well as takes advantage of the semantics of the WhenAny() method. I also removed the AsParallel(); given that the long-running part of the processing is handled asynchronously already, there did not seem to be any advantage in the attempt to parallelize the Select() itself.
The likely cause for the hang is that you are mixing sync and asnyc code and calling WaitAny. Stephen Cleary has post that is useful in understanding common issues with Tasks.
Best Practices in Asynchronous Programming
Here is a solution that simplifies your code and uses Parallel.ForEach
Code
public partial class WaitAnyWindow : Window {
private List<string> URLsToProcess = new List<string>
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon.com",
"http://www.msn.com"
};
public WaitAnyWindow02() {
InitializeComponent();
Parallel.ForEach(URLsToProcess, (x) => DownloadStringFromWebsite(x));
}
private bool DownloadStringFromWebsite(string website) {
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
if (e.Error != null)
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.tbStatusBar.Text = string.Format("{0} didn't complete because {1}", website, e.Error.Message);
}));
}
else
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.tbStatusBar.Text = string.Format("{0} has completed", website);
}));
}
};
client.DownloadStringAsync(new Uri(website));
return true;
}
}
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/
I have a single-threaded program which executes complex, time consuming calculations and reports the progress of these.
private void BtnExecute_Click()
{
ComplexCalculation(Object1);
ComplexCalculation(Object2);
ComplexCalculation(Object3);
}
The ComplexCalculation method looks like this:
private void ComplexCalculation(Object MyObject)
{
(...do some time consuming operation on MyObject...);
WriteToTextboxGUI("50% Complete" + MyObject.Name);
(...do another time consuming operation on MyObject...);
WriteToTextboxGUI("100% Complete" + MyObject.Name);
}
In the above code, the WriteToTextboxGUI(string) method updates a textbox on the GUI with the progress.
I'm looking to adopt a multi-threaded approach so that the GUI remains responsive while the complex calculations are carried out. I have read up quite a bit about BackgroundWorker and Threads in general, and then how this was simplified/improved when Task and TPL came onto the scene with .Net 4.0, and now how Async and Await have arrived with .Net 4.5, and would like to know if these more recent technologies can allow me to re-write my application (in relatively simple code) so that it can:
Use multiple threads simultaneously to execute complex calculations
Report progress to my GUI as before
Maintain a responsive GUI throughout
Can anyone can point me in the direction of a simple solution which would satisfy these three criteria?
P.S. This application will not be running on a server, it will be running in WPF on the desktop.
I recommend that you use IProgress<T> for progress reports and Task.Run to start background tasks. I recently finished a blog series showing that Task.Run is superior to BackgroundWorker.
In your case:
private sealed class ComplexCalculationProgress
{
public string Name { get; set; }
public int PercentComplete { get; set; }
}
private void ComplexCalculation(Object MyObject, IProgress<ComplexCalculationProgress> progress)
{
(...do some time consuming operation on MyObject...);
if (progress != null)
progress.Report(new ComplexCalculationProgress { Name = MyObject.Name, PercentComplete = 50 });
(...do another time consuming operation on MyObject...);
if (progress != null)
progress.Report(new ComplexCalculationProgress { Name = MyObject.Name, PercentComplete = 100 });
}
Note that since your background operation no longer calls WriteToTextboxGUI, it has better separation of concerns. You'll find that the design of IProgress<T> encourages a better design in your own code.
You can call it (using simple parallelism) as such:
private async void BtnExecute_Click()
{
var progress = new Progress<ComplexCalculationProgress>(update =>
{
WriteToTextboxGUI(update.PercentComplete + "% Complete " + update.Name);
});
await Task.WhenAll(
Task.Run(() => ComplexCalculation(Object1, progress)),
Task.Run(() => ComplexCalculation(Object2, progress)),
Task.Run(() => ComplexCalculation(Object3, progress))
);
}
Alternatively, you can easily use "real" parallelism:
private async void BtnExecute_Click()
{
var progress = new Progress<ComplexCalculationProgress>(update =>
{
WriteToTextboxGUI(update.PercentComplete + "% Complete " + update.Name);
});
var objects = new[] { Object1, Object2, Object3 };
await Task.Run(() =>
Parallel.ForEach(objects, o => ComplexCalculation(o, progress))
);
}
Background workers can do all this easily.
Use multiple threads simultaneously to execute complex calculations
Use a new background worker for each calculation.
Report progress to my GUI as before
Use WorkerReportsProgress=True, with the ReportProgress() method and ProgressChanged event.
Maintain a responsive GUI throughout
Yep.