I've got a BackgroundWorker Queue for my Windows Forms app, which works fine with API calls, but querying a local SQLite database is blocking the UI. I realized a while ago that calling Invoke on an action runs it on the UI thread, so I've changed the DoWork function to be a simple function call on the BackgroundWorker's thread.
The problem is in the loadItems() function in my Form1 class. It queues a background worker to query the sqlite database.
What could be causing the UI block and what can I change to prevent it?
My custom Worker class:
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class Worker : BackgroundWorker
{
public Worker(object Sender)
{
this.Sender = Sender;
}
public object Sender { get; set; }
}
}
My Task Manager class (manages the queue)
using System.ComponentModel;
namespace EveMarket.Tasks
{
public class TaskManager
{
public static Queue<Worker> taskQueue = new Queue<Worker>();
public static List<string> errors = new List<string>();
public static void QueueTask(
object item,
Action<object, Worker, DoWorkEventArgs> action,
Action<object, RunWorkerCompletedEventArgs> actionComplete,
Action<RunWorkerCompletedEventArgs> displayError,
Action<object, ProgressChangedEventArgs> progressChanged)
{
using (var worker = new Worker(item))
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.ProgressChanged += (sender, args) =>
{
progressChanged.Invoke(sender, args);
};
worker.DoWork += (sender, args) =>
{
action(sender, worker, args);
};
worker.RunWorkerCompleted += (sender, args) =>
{
if(args.Error != null)
{
displayError.Invoke(args);
} else
actionComplete.Invoke(sender, args);
taskQueue.Dequeue();
if (taskQueue.Count > 0)
{
startQueue();
}
};
taskQueue.Enqueue(worker);
if(taskQueue.Count == 1)
{
startQueue();
}
}
}
private static void startQueue()
{
var next = taskQueue.Peek();
next.ReportProgress(0, "");
next.RunWorkerAsync(next.Sender);
}
}
}
Database service class for Item:
using Microsoft.Data.Sqlite;
using Dapper;
namespace EveMarket.Database
{
public class Item
{
public Item() { }
public Item(int type_id, string name)
{
this.type_id = type_id;
this.name = name;
}
public string name { get; set; }
public int type_id { get; set; }
public static void insert(Item item)
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "insert into item (type_id, name) values (#type_id, #name)";
connection.ExecuteAsync(sql, item);
}
}
public static List<Item> getAll()
{
using (var connection = new SqliteConnection(Connection.ConnectionString))
{
var sql = "select * from item;";
List<Item> items = connection.Query<Item>(sql).ToList();
return items;
}
}
}
}
And the Form which is Queuing these tasks:
using EveMarket.Database;
using EveMarket.Tasks;
using EVEStandard;
namespace EveMarket
{
public partial class Form1 : Form
{
private EVEStandardAPI client;
private List<Item> items;
public Form1()
{
InitializeComponent();
this.items = new List<Item>();
client = new EVEStandardAPI("email#email.com", EVEStandard.Enumerations.DataSource.Tranquility, TimeSpan.FromSeconds(30));
}
private void Form1_Load(object sender, EventArgs e)
{
loadItems();
toolStripProgressBar1.Value = 50;
}
private void updateItemListToolStripMenuItem_Click(object sender, EventArgs e)
{
fetchItems();
}
private void fetchItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
List<long> ids = new List<long>();
for (int i = 1; i < 17; i++)
{
var task = client.Market.ListTypeIdsRelevantToMarketV1Async(10000002, page: i);
var page = task.Result.Model;
ids.AddRange(page);
w.ReportProgress((i * 100) / 16, "Fetching Market Item IDs Page: " + i);
}
List<List<long>> chunks = Utils.ChunkBy(ids, 1000);
int j = 1;
w.ReportProgress((j * 100) / chunks.Count, "Fetching Item Names ");
foreach (var chunk in chunks)
{
var task = client.Universe.GetNamesAndCategoriesFromIdsV3Async(chunk.ConvertAll(i => (int)i));
var names = task.Result.Model;
var types = names.FindAll((item) => item.Category == EVEStandard.Enumerations.CategoryEnum.inventory_type);
foreach (var type in types)
{
Item item = new Item(type.Id, type.Name);
Item.insert(item);
}
w.ReportProgress((j * 100) / chunks.Count, "Fetching Market Item Names Chunk: " + j);
j++;
}
},
// On Complete
(x, e) =>
{
loadItems();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.Result.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
private void loadItems()
{
TaskManager.QueueTask(this,
// DoWork
(x, w, e) =>
{
w.ReportProgress(50, "Loading Items");
List<Item> i = Item.getAll();
w.ReportProgress(100, "Items Loaded");
this.items = i;
},
// On Complete
(x, e) =>
{
foreach (var item in items)
{
itemBindingSource.Add(item);
}
itemBindingSource.EndEdit();
this.toolStripStatusLabel1.Text = "Idle";
this.toolStripProgressBar1.Value = 0;
},
// On Error
(e) =>
{
MessageBox.Show("Error: " + e.ToString());
},
// On Progress Change
(x, e) =>
{
this.toolStripProgressBar1.Value = e.ProgressPercentage;
this.toolStripStatusLabel1.Text = e.UserState.ToString();
});
}
}
}
Turns out it was the RunWorkerCompleted loop blocking the UI. I discovered that I could simply overwrite the datasource with the result of the Worker's execution, which works immediately.
itemBindingSource.DataSource = e.Result;
Related
I am writing a program which is supposed to detect when a USB serial device is plugged in and then log on to the new com port. The code below works wonderfully, but I have noticed in debugging the code and stepping through it that the event handler "DetectChange" fires twice. I'm not sure that this is normal, or an action of the debugger.
In any case, the code works, but I am new at event handling and I would like to make sure that I am not going to cause any issues as I add more code to actually read and write from the serial port.
(I got some of this code from stackoverflow, but I have misplaced my paper with names for attribution. If you see your code below, my heartfelt thanks.)
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
using System.Threading;
namespace SerialTest
{
public partial class Form1 : Form
{
SerialMethods serialMethods = new SerialMethods();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
loadCmdBox();
}
private void CmdBoxPort_SelectedIndexChanged(object sender, EventArgs e)
{
handleComPort();
}
private void handleComPort()
{
// Set the right port for the selected item.
// The portname is based on the "COMx" part of the string (SelectedItem)
string item = CmdBoxPort.SelectedItem.ToString();
// Search for the expression "(COM" in the "selectedItem" string
if (item.Contains("(COM"))
{
// Get the index number where "(COM" starts in the string
int indexOfCom = item.IndexOf("(COM");
// Set PortName to COMx based on the expression in the "selectedItem" string
// It automatically gets the correct length of the COMx expression to make sure
// that also a COM10, COM11 and so on is working properly.
string PortName = item.Substring(indexOfCom + 1, item.Length - indexOfCom - 2);
if (serialMethods._serialPort.IsOpen)
{
serialMethods._serialPort.Close();
serialMethods.Connect(PortName);
label5.Text = "Active Port: " + PortName;
}
else
{
serialMethods.Connect(PortName);
label5.Text = PortName;
}
}
else
return;
}
private void loadCmdBox()
{
// Get all serial (COM)-ports you can see in the devicemanager
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2",
"SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\"");
// Sort the items in the combobox
CmdBoxPort.Sorted = true;
// Add all available (COM)-ports to the combobox
foreach (System.Management.ManagementObject queryObj in searcher.Get().Cast<ManagementObject>())
{
CmdBoxPort.Items.Add(queryObj["Caption"]);
}
SerialPortService.PortsChanged += (sender1, changedArgs) => DetectChange(changedArgs.EventType);
label2.Text = "";
label3.Text = "";
label4.Text = "";
}
protected Task<Task> getSerPorts()
{
CmdBoxPort.Text = "";
CmdBoxPort.Update();
if (!String.IsNullOrEmpty(CmdBoxPort.Text))
{
handleComPort();
return Task.FromResult(Task.CompletedTask);
}
else
{
loadCmdBox();
return Task.FromResult(Task.CompletedTask);
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
SerialPortService.CleanUp();
this.Close();
}
private void RefreshButton_Click(object sender, EventArgs e)
{
refresh();
}
protected Task<Task> refresh()
{
label2.Text = "";
label3.Text = "";
label4.Text = "";
CmdBoxPort.Items.Clear();
getSerPorts();
return Task.FromResult(Task.CompletedTask);
}
protected virtual void DetectChange(EventType changedArgs)
{
if (changedArgs == EventType.Insertion)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at insertion invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
else if (changedArgs == EventType.Removal)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at removal invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
return;
}
}
public static class SerialPortService
{
private static SerialPort _serialPort;
private static string[] _serialPorts;
private static ManagementEventWatcher arrival;
private static ManagementEventWatcher removal;
private static readonly SerialMethods SD = new SerialMethods();
static SerialPortService()
{
_serialPorts = SerialPort.GetPortNames();
MonitorDeviceChanges();
}
public static void CleanUp()
{
arrival.Stop();
removal.Stop();
}
public static event EventHandler<PortsChangedArgs> PortsChanged;
private static void MonitorDeviceChanges()
{
try
{
var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
arrival = new ManagementEventWatcher(deviceArrivalQuery);
removal = new ManagementEventWatcher(deviceRemovalQuery);
arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
MessageBox.Show("Management exception = " + err, "Info", MessageBoxButtons.OK);
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = SerialPort.GetPortNames();
if (eventType == EventType.Insertion)
{
var added = availableSerialPorts.Except(_serialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
}
else if (eventType == EventType.Removal)
{
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
}
}
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
handler?.Invoke(sender, args);
}
}
public enum EventType
{
Insertion,
Removal,
}
public class PortsChangedArgs : EventArgs
{
private readonly EventType _eventType;
private readonly string[] _serialPorts;
public PortsChangedArgs(EventType eventType, string[] serialPorts)
{
_eventType = eventType;
_serialPorts = serialPorts;
}
public string[] SerialPorts => _serialPorts;
public EventType EventType => _eventType;
}
}
Just took a short look at this. It seems like getSerPorts() will always execute loadCmdBox() (CmdBoxPort.Text = ""; ... if (!String.IsNullOrEmpty(CmdBoxPort.Text))) that will attach a new event handler (previous attached event handlers will not be removed by attaching a new one).
You should either remove the existing event handler befor attaching a new one or only attach the event handler once.
have such code.
Start threads:
Thread[] thr;
static object locker = new object();
bool liking = true;
private void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
button3.Enabled = true;
string post = create_note();
decimal value = Program.Data.numericUpDown1;
int i = 0;
int j = (int)(value);
thr = new Thread[j];
for (; i < j; i++)
{
thr[i] = new Thread(() => invite(post));
thr[i].IsBackground = true;
thr[i].Start();
}
}
public void invite(string post)
{
while (liking)
{
if (//some comdition)
exit all threads, and start string post = create_note(); again
}
}
If some condition in invite(string post) comes true I need to stop all threads, and go to string post = create_note(); again, get string post and start threads again.
How to do it?
Instead of manual thread management, use Parallel.For with CancellationToken:
var cts = new CancellationTokenSource();
var options = new ParallelOptions
{
CancellationToken = cts.Token,
MaxDegreeOfParallelism = System.Environment.ProcessorCount
};
var result = Parallel.For(0, j, options, i =>
{
invite(post);
options.CancellationToken.ThrowIfCancellationRequested();
});
When you want to cancel parallel calculations, just call cts.Cancel() from external code.
You can use lock and create a class that manage your threads like that :
public class SyncClass
{
public Thread[] thr;
private int NumberOfWorkingThreads { get; set; }
private object Sync = new object();
public int ThreadNumber { get; private set; }
public event EventHandler TasksFinished;
public SyncClass(int threadNumber)
{
thr = new Thread[threadNumber];
ThreadNumber = threadNumber;
NumberOfWorkingThreads = ThreadNumber;
//LunchThreads(threadNumber);
}
protected void OnTasksFinished()
{
if (TasksFinished == null)
return;
lock (Sync)
{
NumberOfWorkingThreads--;
if (NumberOfWorkingThreads == 0)
TasksFinished(this, new EventArgs());
}
}
public void LunchThreads()
{
string post = create_note();
for (int i = 0; i < ThreadNumber; i++)
{
thr[i] = new Thread(() => invite(post));
thr[i].IsBackground = true;
thr[i].Start();
}
}
private void invite(string post)
{
while (true)
{
if (true)
{
break;
}
}
OnTasksFinished();
}
}
Use the event to notify the end of all threads then the class will be used like that:
private void Operation()
{
var sync = new SyncClass(10);
sync.TasksFinished += sync_TasksFinished;
sync.LunchThreads();
}
void sync_TasksFinished(object sender, EventArgs e)
{
Operation();
}
I want to know when all my async threads have completed so I know when to close my loading form. My code never closes the loading form. I don't know why. I'm unsure how to correctly pass my ManualResetEvent object to the async thread too.
I'm also open to a simpler means to achieve my goal of knowing when to close the loading form.
UPDATE
After reading the advice here I've updated my class. Unfortunetly, it still does not work. I feel closer though. It's just that the callback never fires.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;
namespace BrianTests
{
public class TaskInfo
{
public RegisteredWaitHandle Handle;
public string OtherInfo = "default";
public Form loading;
}
public partial class AsyncControlCreateTest : Form
{
//List<ManualResetEvent> MREs = new List<ManualResetEvent>();
Form loading = new Form() { Text = "Loading...", Width = 100, Height = 100 };
CountdownWaitHandle cdwh;
public AsyncControlCreateTest()
{
InitializeComponent();
}
private void AsyncControlCreateTest_Load(object sender, EventArgs e)
{
loading.Show(this);//I want to close when all the async threads have completed
CreateControls();
}
private void CreateControls()
{
int startPoint= 0;
int threadCount = 2;
cdwh = new CountdownWaitHandle(threadCount);
for (int i = 0; i < threadCount; i++)
{
ManualResetEvent mre = new ManualResetEvent(initialState: true);
UserControl control = new UserControl() { Text = i.ToString() };
control.Load += new EventHandler(control_Load);
Controls.Add(control);
control.Top = startPoint;
startPoint += control.Height;
//MREs.Add(mre);
//mre.Set();//just set here for testing
}
Task.Factory.StartNew(new Action(() =>
{
TaskInfo info = new TaskInfo();
info.loading = loading;
try
{
info.Handle = ThreadPool.RegisterWaitForSingleObject(cdwh, WaitProc, info, 4000, executeOnlyOnce: false);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}));
}
public static void WaitProc(object state, bool timedOut)
{//this callback never occurs...
TaskInfo ti = (TaskInfo)state;
string cause = "TIMED OUT";
if (!timedOut)
{
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
ti.loading.Close();
}
void control_Load(object sender, EventArgs e)
{
RichTextBox newRichTextBox = new RichTextBox();
UserControl control = sender as UserControl;
control.Controls.Add(newRichTextBox);
Task.Factory.StartNew(new Action(() =>
{
Thread.Sleep(2000);
newRichTextBox.Invoke(new Action(() => newRichTextBox.Text = "loaded"));
cdwh.Signal();
}));
}
}
public class CountdownWaitHandle : WaitHandle
{
private int m_Count = 0;
private ManualResetEvent m_Event = new ManualResetEvent(false);
public CountdownWaitHandle(int initialCount)
{
m_Count = initialCount;
}
public void AddCount()
{
Interlocked.Increment(ref m_Count);
}
public void Signal()
{
if (Interlocked.Decrement(ref m_Count) == 0)
{
m_Event.Set();
}
}
public override bool WaitOne()
{
return m_Event.WaitOne();
}
}
}
The problem is that WaitHandle.WaitAll is throwing an exception, which you can see:
try
{
WaitHandle.WaitAll(MREs.ToArray());
}
catch (Exception e) {
MessageBox.Show(e.Message);
throw;
}
The error message is that "WaitAll for multiple handles on a STA thread is not supported."
If you do something like
foreach(var m in MREs)
m.WaitOne();
It will work.
I'm not quite sure why the exception did not crash the application, as I would have hoped. See perhaps How can I get WinForms to stop silently ignoring unhandled exceptions? for this.
Locking the MRE's and moving the WaitAll off the STA thread does the trick.
public partial class AsyncControlCreateTest : Form
{
object locker = new object();
static List<ManualResetEvent> MREs = new List<ManualResetEvent>();
Form loading = new Form() { Text = "Loading...", Width = 100, Height = 100 };
public AsyncControlCreateTest()
{
InitializeComponent();
}
private void AsyncControlCreateTest_Load(object sender, EventArgs e)
{
loading.Show(this);//I want to close when all the async threads have completed
CreateControls();
}
private void CreateControls()
{
int startPoint= 0;
for (int i = 0; i < 100; i++)
{
ManualResetEvent mre = new ManualResetEvent(initialState: false);
UserControl control = new UserControl() { Text = i.ToString() };
control.Load += new EventHandler(control_Load);
Controls.Add(control);
control.Top = startPoint;
startPoint += control.Height;
MREs.Add(mre);
}
Task.Factory.StartNew(new Action(() =>
{
try
{
WaitHandle.WaitAll(MREs.ToArray());
}
catch (Exception ex)
{
MessageBox.Show("error " + ex.Message);
}
finally
{
MessageBox.Show("MRE count = " + MREs.Count);//0 count provides confidence things are working...
loading.Invoke(new Action( () => loading.Close()));
}
}));
}
void control_Load(object sender, EventArgs e)
{
RichTextBox newRichTextBox = new RichTextBox();
UserControl control = sender as UserControl;
control.Controls.Add(newRichTextBox);
Task.Factory.StartNew(new Action(() =>
{
Thread.Sleep(500);
newRichTextBox.Invoke(new Action(() => newRichTextBox.Text = "loaded"));
lock (locker)
{
var ev = MREs.First();
MREs.Remove(ev);
ev.Set();
}
}));
}
}
I'm testing a class that wraps BackgroundWorker to perform an operation away from the UI thread in my application.
The test below fails if Timeout is exceeded and passes if progressEventCount reaches the expected number of events before then.
My question is about synchronization. asyncExecutor.Progressed is fired from the Thread Pool thread that BackgroundWorker is using and the test thread reads it back in the while loop.
Am I using lock correctly?
[Test]
[Timeout(1250)]
public void Execute()
{
var locker = new object();
const int numberOfEvents = 10;
const int frequencyOfEvents = 100;
var start = DateTime.Now;
int progressEventCount = 0;
IGradualOperation tester = new TestGradualOperation(numberOfEvents, frequencyOfEvents);
var asyncExecutor = new AsynchronousOperationExecutor();
asyncExecutor.Progressed += (s, e) => { lock (locker) progressEventCount++; };
asyncExecutor.Execute(tester);
while (true)
{
int count;
lock (locker)
{
count = progressEventCount;
}
if (count < numberOfEvents) continue;
Assert.Pass("Succeeded after {0} milliseconds", (DateTime.Now - start).TotalMilliseconds);
}
}
// Implementation
public class AsynchronousOperationExecutor
{
public void Execute(IGradualOperation gradualOperation)
{
var backgroundWorker = new BackgroundWorker {WorkerReportsProgress = true};
backgroundWorker.DoWork += BackgroundWorkerDoWork;
backgroundWorker.ProgressChanged += BackgroundWorkerProgressChanged;
backgroundWorker.RunWorkerAsync(gradualOperation);
}
private void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myArgs = e.UserState as ProgressEventArgs;
OnProgressed(myArgs);
}
static void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
var workerThis = sender as BackgroundWorker;
var operation = e.Argument as IGradualOperation;
if (workerThis == null || operation == null) return;
operation.Progressed += (s, e1) => workerThis.ReportProgress((int)e1.Percentage, e1);
operation.Run();
}
private void OnProgressed(ProgressEventArgs e)
{
if (Progressed != null)
Progressed(this, e);
}
public event EventHandler<ProgressEventArgs> Progressed;
}
// Test Helper Class
public class TestGradualOperation : IGradualOperation
{
private readonly int _numberOfEvents;
private readonly int _frequencyMilliseconds;
public TestGradualOperation(int numberOfEvents, int frequencyMilliseconds)
{
_numberOfEvents = numberOfEvents;
_frequencyMilliseconds = frequencyMilliseconds;
}
public void Run()
{
for (int i = 0; i < _numberOfEvents; i++)
{
Thread.Sleep(_frequencyMilliseconds);
OnProgressed(new ProgressEventArgs(i, _numberOfEvents));
}
}
private void OnProgressed(ProgressEventArgs e)
{
if (Progressed != null)
Progressed(this, e);
}
public event EventHandler<ProgressEventArgs> Progressed;
}
I think this revision is an improvement, blocking the test thread and signalling with an AutoResetEvent. Not winning any brownie points for test readability though.
[Test]
[Timeout(1250)]
public void Execute()
{
var locker = new object();
EventWaitHandle waitHandle = new AutoResetEvent(false);// <--
const int numberOfEvents = 10;
const int frequencyOfEvents = 100;
var start = DateTime.Now;
int progressEventCount = 0;
IGradualOperation tester = new TestGradualOperation(numberOfEvents, frequencyOfEvents);
var asyncExecutor = new AsynchronousOperationExecutor();
asyncExecutor.Progressed += (s, e) =>
{
lock (locker)
{
progressEventCount++;
waitHandle.Set();// <--
}
};
asyncExecutor.Execute(tester);
while (true)
{
waitHandle.WaitOne();// <--
if (progressEventCount < numberOfEvents) continue;
Assert.Pass("Succeeded after {0} milliseconds", (DateTime.Now - start).TotalMilliseconds);
}
}
I've got a method called Import that imports the data of an xml file into the database. It looks like this:
private void SavingXMLFile()
{
//Save the file:
if (rootElement != null)
{
try
{
using (FileStream fsHosp = new FileStream("Data/ConfigOrgHospital.xml", FileMode.Truncate, FileAccess.Write))
{
using (XmlWriter x = XmlWriter.Create(fsHosp))
{
SerializeHospitalData(x, rootElement);
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
try
{
Import("Hospitals");
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
Then it goes to the import method:
public void Import(string sourceFile)
{
IProgressReporter progressReporter = new ProgressReporter();
BackgroundThreading.RunInBackground<int>((object input) =>
{
foreach (IConfigRunner runner in Runners.OrderBy(r => r.Priority))
{
runner.DoImport(progressReporter, sourceFile);
}
return 0;
}, (answer, error, w) =>
{
if (error != null)
{
//ShowError(error);
}
else
{
//AddProgress("Export ready");
}
//DoReady();
}, (error) =>
{
//ShowError(error);
});
}
Then it does a DoImport:
public interface IConfigRunner
{
int Priority { get; }
void DoExport(IProgressReporter progress);
void DoImport(IProgressReporter progress, string filename);
}
[Export]
public void DoImport(IProgressReporter progress, string filename = null)
{
if (filename.Equals("Hospitals"))
{
if (File.Exists(HOSPITALFILE))
{
progress.AddProgress("Importing " + HOSPITALFILE);
using (OrgEntities orgEntityModel = ModelFactory.GetOrgEntities())
{
Tools.ValidateXml(HOSPITALFILE, "Xsd\\hospital.xsd");
XmlSerializer inSerializer = new XmlSerializer(typeof(HospitalRoot));
TextReader reader = new StreamReader(HOSPITALFILE);
HospitalRoot root = (HospitalRoot)inSerializer.Deserialize(reader);
reader.Close();
try
{
OrgHospitalXml.Insert(orgEntityModel, root.Hospitals);
}
catch (ImportException e)
{
progress.AddProgress(e.Message + ": " + e.Item);
throw e;
}
}
}
}
Is there any way that I can show the progression of this in a progressbar? Or how to find all the UI Threads? Thx in advance
BackgroundWorker:
public static class BackgroundThreading
{
public static BackgroundWorker RunInBackground<T>(object param, Func<object, T> call, Action<T, Exception, BackgroundWorker> callBack, Action<Exception> errorHandler = null) where T : new()
{
BackgroundWorker worker = new BackgroundWorker();
DoWorkEventHandler workHandler = null;
RunWorkerCompletedEventHandler completeHandler = null;
workHandler = delegate(object s, DoWorkEventArgs args)
{
args.Result = call(args.Argument);
args.Cancel = worker.CancellationPending;
};
completeHandler = delegate(object s, RunWorkerCompletedEventArgs args)
{
if (!args.Cancelled)
{
if (args.Error != null)
{
if (!(args.Error is FaultException))
{
T result = new T();
callBack(result, args.Error, (BackgroundWorker)s);
}
else
{
if (errorHandler == null)
{
string message;
if (args.Error.InnerException != null)
{
message = args.Error.InnerException.Message;
}
else
{
message = args.Error.Message;
}
Logger.LogError("SVC", Logger.SVC_ERROR_001, new object[1] { message });
throw args.Error;
}
else
{
errorHandler(args.Error);
}
}
}
else
{
callBack((T)args.Result, null, (BackgroundWorker)s);
}
((BackgroundWorker)s).DoWork -= workHandler;
((BackgroundWorker)s).RunWorkerCompleted -= completeHandler;
}
};
worker.DoWork += workHandler;
worker.RunWorkerCompleted += completeHandler;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync(param);
return worker;
}
public static BackgroundWorker RunInBackground<T>(Func<object, T> call, Action<T, Exception, BackgroundWorker> callBack, Action<Exception> errorHandler = null) where T : new()
{
return RunInBackground<T>(null, call, callBack, errorHandler);
}
Use BackgroundWorker http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
ProgressChanged event is automatically run on UI thread while your actual code runs in a different thread.
As far as i remember you need to hook up to the progress changed event and update your progress bar in this.
You then need to call worker.reportProgress(x).
Another option is to set your progress bar to marque and call Application.DoEvents every now and then but this may well be frowned upon by other members