Asynchronous operations within a loop - how to keep control of execution? - c#

Follow-on question to this one.
I am trying to generate and save a series of images. Rendering is done by Helix Toolkit, which I am told utilises the WPF composite render thread. This is causing problems because it executes asynchronously.
My original problem was that I couldn't save a given image because it hadn't yet been rendered at that time I was trying to save it. The above answer provides a workaround to this by putting the 'save' operation inside an Action which is called with low priority, thus ensuring that the rendering completes first.
This is fine for one image, but in my application I require multiple images. As it stands I cannot keep control of the sequence of events because they occur asynchronously. I am using a For loop which just continues regardless of the progress of rendering and saving the images. I need the images to be generated one by one, with enough time for rendering and saving before starting the next one.
I have tried putting delays in the loop but that causes its own problems. For instance an async await as commented in the code causes cross-threading issues because the data was created on a different thread from where the rendering is being done. I tried putting in a simple delay but then that just locks everything up - I think in part because the save operation I am waiting on has very low priority.
I cannot simply treat it as a batch of separate unrelated asynchronous tasks because I am using a single HelixViewport3D control in the GUI. The images have to be generated sequentially.
I did try a recursive method where SaveHelixPlotAsBitmap() calls DrawStuff() but that wasn't working out very well, and it doesn't seem a good approach.
I tried setting a flag ('busy') on each loop and waiting for it to be reset before continuing but that wasn't working - again, because of the asynchronous execution. Similarly I tried using a counter to keep the loop in step with the number of images that had been generated but ran into similar problems.
I seem to be going down a rabbit hole of threading and asynchronous operations that I don't want to be in.
How can I resolve this?
class Foo {
public List<Point3D> points;
public Color PointColor;
public Foo(Color col) { // constructor creates three arbitrary 3D points
points = new List<Point3D>() { new Point3D(0, 0, 0), new Point3D(1, 0, 0), new Point3D(0, 0, 1) };
PointColor = col;
}
}
public partial class MainWindow : Window
{
int i = -1; // counter
public MainWindow()
{
InitializeComponent();
}
private void Go_Click(object sender, RoutedEventArgs e) // STARTING POINT
{
// Create list of objects each with three 3D points...
List<Foo> bar = new List<Foo>(){ new Foo(Colors.Red), new Foo(Colors.Green), new Foo(Colors.Blue) };
foreach (Foo b in bar)
{
i++;
DrawStuff(b, SaveHelixPlotAsBitmap); // plot to helixViewport3D control ('points' = list of 3D points)
// This is fine the first time but then it runs away with itself because the rendering and image grabbing
// are asynchronous. I need to keep it sequential i.e.
// Render image 1 -> save image 1
// Render image 2 -> save image 2
// Etc.
}
}
private void DrawStuff(Foo thisFoo, Action renderingCompleted)
{
//await System.Threading.Tasks.Task.Run(() =>
//{
Point3DCollection dataList = new Point3DCollection();
PointsVisual3D cloudPoints = new PointsVisual3D { Color = thisFoo.PointColor, Size = 5.0f };
foreach (Point3D p in thisFoo.points)
{
dataList.Add(p);
}
cloudPoints.Points = dataList;
// Add geometry to helixPlot. It renders asynchronously in the WPF composite render thread...
helixViewport3D.Children.Add(cloudPoints);
helixViewport3D.CameraController.ZoomExtents();
// Save image (low priority means rendering finishes first, which is critical)..
Dispatcher.BeginInvoke(renderingCompleted, DispatcherPriority.ContextIdle);
//});
}
private void SaveHelixPlotAsBitmap()
{
Viewport3DHelper.SaveBitmap(helixViewport3D.Viewport, $#"E:\test{i}.png", null, 4, BitmapExporter.OutputFormat.Png);
}
}

Note These examples are just to prove a concept, there is work needed on the TaskCompletionSource to handle errors
Given this test window
<Window x:Class="WpfApp2.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"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel x:Name="StackPanel"/>
</Grid>
</Window>
Here is an example of how to use events to know when the view is in the state that you want.
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DoWorkAsync();
}
private async Task DoWorkAsync()
{
for (int i = 0; i < 10; i++)
{
await RenderAndCapture();
}
}
private async Task RenderAndCapture()
{
await RenderAsync();
CaptureScreen();
}
private Task RenderAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
var panel = new TextBlock {Text = "NewBlock"};
panel.Loaded += OnPanelOnLoaded;
StackPanel.Children.Add(panel);
void OnPanelOnLoaded(object sender, RoutedEventArgs args)
{
panel.Loaded -= OnPanelOnLoaded;
taskCompletionSource.TrySetResult(null);
}
});
return taskCompletionSource.Task;
}
private void CaptureScreen()
{
// Capture Image
}
}
}
If you want to have your sync method called from outside you can implement a task queue.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp2
{
public class TaskQueue
{
private readonly SemaphoreSlim _semaphore;
public TaskQueue()
{
_semaphore = new SemaphoreSlim(1);
}
public async Task Enqueue(Func<Task> taskFactory)
{
await _semaphore.WaitAsync();
try
{
await taskFactory();
}
finally
{
_semaphore.Release();
}
}
}
public partial class MainWindow : Window
{
private readonly TaskQueue _taskQueue;
public MainWindow()
{
_taskQueue = new TaskQueue();
InitializeComponent();
DoWork();
}
private void DoWork()
{
for (int i = 0; i < 10; i++)
{
QueueRenderAndCapture();
}
}
private void QueueRenderAndCapture()
{
_taskQueue.Enqueue(() => RenderAndCapture());
}
private async Task RenderAndCapture()
{
await RenderAsync();
CaptureScreen();
}
private Task RenderAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
var panel = new TextBlock {Text = "NewBlock"};
panel.Loaded += OnPanelOnLoaded;
StackPanel.Children.Add(panel);
void OnPanelOnLoaded(object sender, RoutedEventArgs args)
{
panel.Loaded -= OnPanelOnLoaded;
taskCompletionSource.TrySetResult(null);
}
});
return taskCompletionSource.Task;
}
private void CaptureScreen()
{
// Capture Screenshot
}
}
}
This will make sure the UI is in the state required for each iteration
You will of course need to expand this so that you listen to the Loaded event of each point that you wish to render.
Edit:
As PointsVisual3D does not have the Loaded event you can complete the task by hooking onto the event you had previously used. Not ideal, but it should work.
private Task RenderAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
var panel = new TextBlock {Text = "NewBlock"};
StackPanel.Children.Add(panel);
Dispatcher.BeginInvoke(new Action(() =>
{
taskCompletionSource.TrySetResult(null);
}), DispatcherPriority.ContextIdle);
});
return taskCompletionSource.Task;
}

Solution below. This is my implementation of the code provided in Jason's answer. All credit to Jason for the important bits.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Go_Click(object sender, RoutedEventArgs e) // STARTING POINT
{
DoWorkAsync();
}
private async Task DoWorkAsync()
{
// Create list of objects each with three 3D points...
List<Foo> bar = new List<Foo>() { new Foo(Colors.Red), new Foo(Colors.Green), new Foo(Colors.Blue) };
int i = -1; // init counter
foreach (Foo b in bar)
{
i++;
await RenderAndCapture(b, i);
}
}
private async Task RenderAndCapture(Foo b, int i)
{
await RenderAsync(b);
SaveHelixPlotAsBitmap(i);
}
private Task RenderAsync(Foo b)
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
DrawStuff(b);
Dispatcher.BeginInvoke(new Action(() =>
{
taskCompletionSource.TrySetResult(null);
}), DispatcherPriority.ContextIdle);
});
return taskCompletionSource.Task;
}
private void DrawStuff(Foo thisFoo)
{
Point3DCollection dataList = new Point3DCollection();
PointsVisual3D cloudPoints = new PointsVisual3D { Color = thisFoo.PointColor, Size = 5.0f };
foreach (Point3D p in thisFoo.points)
{
dataList.Add(p);
}
cloudPoints.Points = dataList;
// Add geometry to helixPlot. It renders asynchronously in the WPF composite render thread...
helixPlot.Children.Add(cloudPoints);
helixPlot.CameraController.ZoomExtents();
}
private void SaveHelixPlotAsBitmap(int i) // screenshot
{
Viewport3DHelper.SaveBitmap(helixPlot.Viewport, $#"E:\test{i}.png", null, 4, BitmapExporter.OutputFormat.Png);
}
}

Related

Async-await in multiple Winforms projects

We're trying to get status updates in the UI while the threads are running in the background. The following code is supposed to allow it but in practice we get the updates only once all threads are done and not while they are running. We also don't see significant performance improvement compared to running the task in serial so we might be doing something wrong here
The solution includes two projects with winForm with the first calling the second. WinClient namespace is used for the Winform client. It calls Services.modMain:
namespace WinClient
{
static class Program
{
[STAThread]
static void Main()
{
//call another winform project and wait for it to complete
Services.modMain.loadObjects().Wait();
//run local form
Application.Run(new Form1());
}
}
}
Service.modMain is where the application is continuously getting data and updating it in memory. When it does, it writes status messages to a splash form which remains open all the time. Once Service.modMain finishes the initial data load, Form1 (empty form in this exampl) should open while splashForm remains open as well
namespace Services
{
public static class modMain
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main()
{
}
public static async Task loadObjects()
{
frmSplash.DefInstance.LoadMe();
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
frmSplash.DefInstance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
frmSplash.DefInstance.PrintToSplashWindow(e);
}
}
}
PrintToSplashWindow is just a utility class to store progress data:
namespace Services
{
public class PrintToSplashMessage
{
public string Message { get; set; }
public Color MessageColor { get; set; }
public bool OnNewLine { get; set; }
public bool PrintToLog { get; set; }
public PrintToSplashMessage(String theMessage, Color theMessageColor, bool isOnNewLine, bool needPrintToLog)
{
Message = theMessage;
MessageColor = theMessageColor;
OnNewLine = isOnNewLine;
PrintToLog = needPrintToLog;
}
}
}
Finally, here's frmSplash:
namespace Services
{
public partial class frmSplash : Form
{
public frmSplash() :base()
{
InitializeComponent();
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if ( rtbErrorDisplay.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.SelectionColor = lngColor;
rtbErrorDisplay.SelectedText = strNewLine + strShortMsg;
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.ScrollToCaret();
Application.DoEvents();
}
}
}
What we expect is that frmSplash would show the progress messages as the tasks are runing in the background. In practice, it only show all at bulk when everything is done.
Short version: the only thing that ever processes window messages in the code you posted is a call to Application.DoEvents(). But the code likely never gets that far, or if it does, the call happens on the wrong thread.
Longer version:
You didn't include an actual MCVE, so I didn't bother to test, but the Progress class relies on a synchronization context to work. Since you haven't called Application.Run(), there's may be no sync context at all. In which case Progress is just going to use the thread pool to invoke whatever handlers subscribed to it.
That would mean that when you call Application.DoEvents(), you're in a thread pool thread, not the thread that owns your splash window.
Windows are owned by threads, and their messages go to that thread's message queue. The Application.DoEvents() method will retrieve messages for the current thread's message queue, but does nothing to process messages for other threads' queues.
In the worst case, there is a sync context for that thread (I can't recall…it's possible that since the thread is STA, the framework has created one for you), but since you have no message loop, nothing queued to it ever gets dispatched. The progress reports just keep piling up and never processed.
You should abandon Application.DoEvents() altogether. Calling DoEvents() is always a kludge, and there's always a better option.
In this case, use Application.Run() for the first form as well (the splash screen). Create that form and subscribe to its FormShown event so that you know when to call loadObjects(). At the end of that method, close the form, so Application.Run() will return and go on to the next Application.Run() call.
Here is a sample based on the code you did post, with me filling in the details (for both forms, just use the Designer to create a default Form object…the rest of the initialization is in the user code below).
For the splash screen class, I inferred most of it, and took the rest straight from your code. The only change I made to your code was to remove the call to Application.DoEvents():
partial class SplashScreen : Form
{
public static SplashScreen Instance { get; } = new SplashScreen();
private readonly RichTextBox richTextBox1 = new RichTextBox();
public SplashScreen()
{
InitializeComponent();
//
// richTextBox1
//
richTextBox1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
richTextBox1.Location = new Point(13, 13);
richTextBox1.Name = "richTextBox1";
richTextBox1.Size = new Size(775, 425);
richTextBox1.TabIndex = 0;
richTextBox1.Text = "";
Controls.Add(richTextBox1);
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if (richTextBox1.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.SelectionColor = lngColor;
richTextBox1.SelectedText = strNewLine + strShortMsg;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
It's not clear to me why you have two different classes, both of which seem to be set up as the entry point for the program. I consolidated those into a single class:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
loadObjects();
Application.Run(new Form1());
}
public static void loadObjects()
{
SplashScreen.Instance.Shown += async (sender, e) =>
{
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
SplashScreen.Instance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
SplashScreen.Instance.Close();
};
SplashScreen.Instance.ShowDialog();
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load2, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load1, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
SplashScreen.Instance.PrintToSplashWindow(e);
}
}

Prioritized threads not doing their job in expected order

at school I was given an assignment to build a simple application to show how threads prioritizing work. Problem is that threads I fire doesn't stick to priorities set and finish at random (or it looks like so).
Here's code for my main window:
namespace ThreadsShowtime
{
public partial class MainWindow : Window
{
private SuperThread _lowest;
private SuperThread _below;
private SuperThread _normal;
private SuperThread _above;
private SuperThread _highest;
public MainWindow()
{
InitializeComponent();
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
_lowest = new SuperThread(ThreadPriority.Lowest, elipseLowest, Dispatcher, lowestLbl);
_below = new SuperThread(ThreadPriority.BelowNormal, elipseBelow, Dispatcher, belowLbl);
_normal = new SuperThread(ThreadPriority.Normal, elipseNormal, Dispatcher, normalLbl);
_above = new SuperThread(ThreadPriority.AboveNormal, elipseAbove, Dispatcher, aboveLbl);
_highest = new SuperThread(ThreadPriority.Highest, elipseHighest, Dispatcher, highestLbl);
_lowest.Start();
_below.Start();
_normal.Start();
_above.Start();
_highest.Start();
}
}
}
And SuperThread class code:
namespace ThreadsShowtime
{
public class SuperThread
{
private readonly Stopwatch _stopwatch;
public readonly Thread Thread;
public SuperThread(ThreadPriority thp, Shape sh, Dispatcher dispatcher, ContentControl lbl)
{
Thread = new Thread(() => DummyMethod(dispatcher, sh, lbl)) {Priority = thp};
_stopwatch = new Stopwatch();
}
public long Elapsed { get; private set; }
public double ElapsedSeconds => Elapsed / 1000.0;
private void DummyMethod(Dispatcher dispatcher, Shape sh, ContentControl lbl)
{
_stopwatch.Start();
dispatcher.Invoke(() =>
{
sh.Fill = Brushes.GreenYellow;
}, DispatcherPriority.Normal);
for (var idx = 0; idx < 30000; idx++) Console.WriteLine(idx);
dispatcher.Invoke(() =>
{
sh.Fill = Brushes.OrangeRed;
}, DispatcherPriority.Normal);
_stopwatch.Stop();
Elapsed = _stopwatch.ElapsedMilliseconds;
dispatcher.Invoke(() =>
{
lbl.Content = ElapsedSeconds;
}, DispatcherPriority.ContextIdle);
}
public void Start()
{
Thread.Start();
}
}
}
The output of this application looks like that:
Why does that happen?
Thank you very much for any help provided.
Because of dispatcher.Invoke() and Console.WriteLine(), your code is effectively single threaded. What you see is the slightly unpredictable results of queuing the threads. Those queues are not FIFO.
To see the proper effect, benchmark some code that is purely CPU bound, like a Sort(). Make sure they run for > 200 ms, smaller runs are meaningless.

User interaction in non-UI thread?

in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank
You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.
If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.
Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.
From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?

Progress bar working but label is not updating

I have a library with an Interface.
Public Interface Progress
{
int ProgressValue{get;set;},
string ProgressText{get;set;},
}
Library has a method Create (dummy code):
Public Class TestLibrary
{
Progress _progress;
Public void Create()
{
foreach(var n in TestList)
{
// Do Something
_progress.ProgressValue = GetIndex(n);
_progress.ProgressText = "Updating..." + n;
}
}
}
I have a project that references this library and calls Create method. It even Implements Interface Progress.
Public Class TestProject : Progress
{
public int ProgressValue
{
get{return progressBar1.Value;}
set{progressBar1.Value = value;}
}
public int ProgressText
{
get{return label1.Text;}
set{label1.Text = value;}
}
}
Now when I run the application, Progress Bar behaves properly and shows the progress correctly, but the Text of label1 does not change at all. But it do change in the end of for loop and shows the last item in loop. Can anyone help me out in this?
Note: All these codes are written directly without testing as I don't have my application now with me. Sorry for any syntax errors.
It sounds like all your work is being done on the UI thread. Don't do that.
Instead, run the loop itself in a background thread, and use Control.Invoke or Control.BeginInvoke (probably in the Progress implementation) to marshal a call across to the UI thread just to update the UI. This will leave your UI responsive (and able to update labels etc) while it's still processing.
Used a Label instead of ProgressBar. You can try this code [using BackGroundWorker] -
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
}
Posted the same a few days ago here
The code you posted uses one thread. That means every operation is excuted in sequence right after the previous one finished.
Since you can update GUI elements right away, I suppose the code to be run from main thread (a.k.a "GUI thread"). Blocking the GUI thread results in the GUI ("Graphical User Interface") not updating until there is some idle time for it.
Use following example to refresh your label every iteration so that it updates your UI.
label1.Text = i.ToString();
label1.Refresh();

Multiple async calls to UI thread to update textblock

I'm writing a fighter generation page for my game. The page is supposed to update the UI with randomized values for strength and other attributes while the fighter is being downloaded from the server.
Code so far:
public partial class FighterGenerationPage : PhoneApplicationPage
{
Fighter fighter = null;
string Code = "";
BackgroundWorker worker;
public FighterGenerationPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(FighterGenerationPage_Loaded);
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
}
void FighterGenerationPage_Loaded(object sender, RoutedEventArgs e)
{
AddFighter();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
NavigationContext.QueryString.TryGetValue("code", out Code);
if ("".Equals(Code))
if (NavigationService.CanGoBack)
NavigationService.GoBack();
base.OnNavigatedTo(e);
}
private void AddFighter()
{
WebProxy.GetInstance().AddFighter(AddFighter_Handler, Code);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
Random rand = new Random();
while (fighter == null)
{
int strength = rand.Next(100);
Dispatcher.BeginInvoke(() => { StrValue.Text = Convert.ToString(strength); });
Thread.Sleep(100);
}
Dispatcher.BeginInvoke(() => { StrValue.Text = Convert.ToString(fighter.Strength); });
}
public void AddFighter_Handler(Response response)
{
#if DEBUG
Thread.Sleep(3000);
#endif
if (response.Status.Error == false)
{
fighter = response.Fighter;
}
}
}
This code does almost do what I want, but instead of updating the UI every 0.1 sec it does it one time at start and then many times just before it sets it to the final value (fighter.Strength).
Why this behavior?
What I think is happening is this:
You are running in Debug, which means that your Thread.Sleep code in AddFighter_Handler() gets executed.
The call to AddFighter comes back very quickly.
When you Thread.Sleep() in AddFighterHandler, you are actually blocking the user thread, thus never actually calling the code you pass into BeginInvoke.
Here's the simplest thing you can do to solve this:
public void AddFighter_Handler(Response response)
{
System.Threading.ThreadPool.QueueUserWorkItem(o => {
#if DEBUG
Thread.Sleep(3000);
#endif
if (response.Status.Error == false)
{
fighter = response.Fighter;
}});
}
This will make sure you do not block the thread.
It's also probably not the right thing to - consider using the DispatcherTimer to get something like that pause you are trying to achieve..

Categories

Resources