Singleton Async Loading List Property - c#

I have a winforms application with a singleton class that contains a property (list) that is the datasource for a search grid. Filling this property takes a significant amount of time (>1 minute) so, I want to start filling this property asynchronously when the user launches the program.
The main form has a button to launch another search form. If, when the user launches the search if the datasource is ready then, no problem. However, if the datasource is still filling, the user sees a wait cursor and the search grid should fill as soon as the datasource has finished populating.
To do this, I created a method that fires after the asynchronous method completes and then the grid is bound to the datasource.
Everything appears to work correctly, the event fires, and then I try to bind the list to the grid and nothing happens... Debugging halts and I never hit the next line of code (see comments in FrmSearch.cs).
Any ideas relating to what is going wrong or general code improvements would be very much appeciated, thanks!
Program.cs
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Task.Run(async() => { Singleton.DogsList = await Dogs.FindAllAsync(); });
Application.Run(new FrmMain());
}
}
Singleton.cs
public static class Singleton
{
public static event DogsListHandler DogsListLoaded;
public delegate void DogsListHandler(object sender, EventArgs e);
public static BindingList<Dogs> DogsList
{
get
{
if (dogsList == null)
{
Task.Run(async () =>
{
dogsList = await Dogs.FindAllAsync();
notifyListLoaded();
});
}
return dogsList;
}
set { dogsList = value; }
}
private static BindingList<Dogs> dogsList;
private static void notifyListLoaded()
{
if (DogsListLoaded != null) { DogsListLoaded(null, EventArgs.Empty); }
}
}
FrmSearch.cs
public partial class FrmSearch : Form //launched using the .Show() method from a button on the main form
{
public FrmSearch()
{
InitializeComponent();
}
private void FrmSearch_Load(object sender, EventArgs e)
{
Singleton.DogsListLoaded += new Singleton.DogsListHandler(Dogs_ListLoaded);
Cursor = Cursors.WaitCursor;
if (Singleton.DogsList != null)
{
grid.DataSource = Singleton.DogsList;
Cursor = Cursors.Default;
}
else { Cursor = Cursors.WaitCursor; }
}
public void Dogs_ListLoaded(object sender, EventArgs e)
{
grid.DataSource = Singleton.DogsList; //freezes here
Cursor = Cursors.Default; //this line never gets hit
}
}
Dogs.cs (will pull from db normally but just doing some iteration for the sample)
public class Dogs
{
public string Name { get; set; }
public string Breed { get; set; }
public int Age { get; set; }
public string Summary { get { return string.Format("Name: {0} / Breed: {1} / Age: {2}", Name, Breed, Age.ToString()); } }
public static async Task<BindingList<Dogs>> FindAllAsync()
{
BindingList<Dogs> dl = new BindingList<Dogs>();
await Task.Run(() =>
{
int i = 0;
while (i <= 999999)
{
dl.Add(new Dogs() { Name = "River" + i.ToString(), Breed = "Border Collie", Age = 3 });
dl.Add(new Dogs() { Name = "Jack" + i.ToString(), Breed = "Labrador", Age = 2 });
dl.Add(new Dogs() { Name = "Emma" + i.ToString(), Breed = "Beagle", Age = 7 });
i++;
}
});
return dl;
}
}

You should retrieve the list as you would retrieve all other lists getting from anywhere:
Call a async method and await that.
Easy to handle
public static class SomeLookups
{
private static object _lock = new object( );
private static Task<IList<string>> _foosAsync;
public static Task<IList<string>> GetFoosAsync()
{
lock ( _lock )
{
return _foosAsync = _foosAsync ?? PrivateGetFoosAsync( );
}
}
private static async Task<IList<string>> PrivateGetFoosAsync()
{
var list = new List<string>( );
for ( int i = 0; i < 20; i++ )
{
await Task.Delay( 200 ).ConfigureAwait( false );
list.Add( "item " + i );
}
return list.AsReadOnly( );
}
}
In the consuming class
private async Task RetrieveAllData()
{
IsBusy = true;
MyFooCollection = await SomeLookups.GetFoosAsync();
IsBusy = false;
}
Once the task is finished you can still await it. But because it is finished you get the result without any delay.

Related

Updating Winforms Label with Timer and Thread, stock app

Gist of it has probably been asked before, but I'm completely lost so I'm looking for some personal guidance. Been trying to make a stock tracker app for funsies using WinForms and the Yahoo API. Trying to get it so you can input a tracker symbol and it will make a new Label that will keep updating itself every so often. However, it keeps giving me error messages about "Cross-thread operation not valid". I've tried to do some googling, but yeah, completely lost. Here is most of the code, hope you guys can make some sense of it.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
{
public partial class Form1 : Form
{
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
}
}
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
public async void do_Things(string result)
{
string price;
Label label = null;
if (label == null)
{
price = await getStockPrices(result);
label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.Controls.Add(label);
}
else
{
Thread testThread = new Thread(async delegate ()
{
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
});
}
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
}
private void uiLockingTask() {
Thread.Sleep(5000);
}
}
}
Let me point out several things in your implementation.
You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
public partial class Form1 : Form
{
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}
private void onCancelClick()
{
_cancellationToken.Cancel();
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}
public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}
A much easier way is using async these days.
Here is a class which triggers an Action every interval:
public class UITimer : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
}
}
catch (OperationCanceledException) { }
}
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
}
If you work with dotnet 3.0 or higher, you can use the IAsyncDisposable. With this you're able to await the DisposeAsync method, so you can await the _timerTask to be finished.
And I created a new form with this as code behind:
public partial class Form1 : Form
{
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
{
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
}
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
{
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
}
}
The advantage is, that the callback runs on the UI thread but doesn't block it. The await Task.Delay(..) is using a Timer in the background, but posts the rest of the method/statemachine on the UI thread (because the UI thread has a SynchronizaionContext)
Easy but does the trick ;-)

Problem with adding item to the list from another form

I would like to click "add button" to add contacts to the list of contacts in ClientApp, but nothing added to box, only to a list named users.
I want to see new usser nickname on the listbox. But when I evokes function AddContact from other form I don't see new usser on the listbox, from this same is good.
In atributes I see this cell, named "dwa".
Someone will help?
AddUsser:
public partial class NewUser: Form
{
...
public void New()
{
ClientApp.users.Add(new accounts(textBox1.Text, textBox2.Text));
ClientApp x = new KlientApp();
x.AddContact(textBox2.Text);
this.Hide();
}
}
ClientApp:
public partial class ClientApp: Form
{
...
public void AddContact(string nick)
{
contacts.BeginUpdate();
contacts.Items.Add(nick);
contacts.EndUpdate();
}
}
enter image description here
To Gellio Gao.
I sew ObjectDisposedException in private void Msg when I close program on go:
private void ShowMsg()
{
bool temp = true;
while( temp == true)
{
if(DateTime.Now.Second % 3 == 0)
{
Msg();
showed.WaitOne();
showed.Reset();
showed.WaitOne(1000);
}
}
}
private void Msg()
{
ClientLog.send_msg= "Wyswietl wiadomosci";
ClientLog.received.Reset();
Thread wątek = new Thread(new ThreadStart(AsynchronousClient.StartClient));
wątek.IsBackground = true;
wątek.Start();
ClientLog.received.WaitOne();
Invoke(new Action(() =>
{
if (ClientLog.send_msg!= "")
{
messages.AppendText(ClientLog.send_msg+ Environment.NewLine);
}
}));
showed.Set();
}
You should pass an instance of ClientApp to NewUser instead of creating a new instance inside of NewUser. Something like:
public partial class NewUser: Form
{
private ClientApp _client;
public NewUser(ClientApp client)
{
this._client = client;
}
................
public void New()
{
ClientApp.users.Add(new accounts(textBox1.Text, textBox2.Text));
this._client.AddContact(textBox2.Text);
this.Hide();
}
}
Update: Give a sample of using this._client.

Referenced data is reset to its default values after being copied

I have an issue with data seemingly being reset to its default values.
The class is as follows (objectIDs is a simple enumeration):
public class Output_args: EventArgs {
public objectIDs outputtype;
public int internalID;
public int verdict;
public int outputID;
public long entrytime;
public Output_args Copy() {
Output_args args = new Output_args();
args.entrytime = this.entrytime;
args.internalID = this.internalID;
args.outputID = this.outputID;
args.outputtype = this.outputtype;
args.verdict = this.verdict;
return args;
}
}
The following code creates the object. It runs in a specific thread, let's say Thread1.
class Class1 {
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
public event EventHandler<Output_args> newOutput;
public void readInput(){
List<Output_args> newoutputlist = new List<Output_args>();
/*
* code to determine the outputs
*/
Output_args args = new Output_args();
args.outputtype = objectIDs.stepID;
args.internalID = step[s].ID;
args.verdict = verdict;
args.entrytime = System.DateTime.Now.Ticks;
newoutputlist.Add(args.Copy());
if (newOutput != null && newoutputlist.Count > 0) {
// several outputs are being sent sequentially but for simplicity i've removed the for-loop and decision tree
try {
newOutput(null, newoutputlist[0].Copy());
} catch (Exception) { }
}
}
}
1 of the subscribers to this event has the following code. The processor method runs on a thread of a camerafeed. The newOutput event handler is being run on Thread1.
class Class2: Form {
private Output_args lastoutput = new Output_args();
public void newOutput(object sender, Output_args args) {
lock (lastoutput) {
lastoutput = args.Copy();
}
}
public void processor(){
lock (lastoutput) {
if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
When the eventhandler 'newOutput' of Class2 is being called, the debugger shows that the copy works as expected and 'entrytime' is given the expected number of ticks.
However, when the processor method wants to read the 'entrytime', its value is 0. All other fields also have their default value assigned.
I've tried replacing the object 'lastoutput' with a simple field of the type long and removed the locks but the results are the same: it gets assigned properly in 'newOutput' but has its default value (0) in the processor method.
Any ideas on why this is happening?
you should not lock on the object lastoutput, but on another object, because you reassign the field.
The processor start and lock on the default field instance new Output_args() initialized with default values
class Class2: Form {
private object mylock = new object();
private Output_args lastoutput;
public void newOutput(object sender, Output_args args) {
lock (mylock) {
lastoutput = args.Copy();
}
}
public void processor(){
lock (mylock) {
if (lastoutput == null) {
//nothing to consume yet
}
else if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
but this discard lastouput if consumer is slower than producer. You can use a queue ( or another collection ) as buffer if needed.
class Class2 {
private Queue<Output_args> outputs = new Queue<Output_args>();
public void newOutput(object sender, Output_args args) {
lock (outputs) {
outputs.Enqueue(args.Copy());
}
}
public void processor(){
lock (outputs) {
if (outputs.Count > 0) {
var lastoutput = outputs.Dequeue();
if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
}
demo: https://dotnetfiddle.net/daHVD1

How to organize a HIERARCHY of non-UI threads and get status and progress back for UI consumption

Figure 1 - Working Demo
I'm running a winforms application and I have implemented a status / progress TreeView. It can display the status (by icon) and progress of a (possibly hierarchical) set of tasks to accomplish. My question is not about how to implement the TreeView control itself. I've got that part covered. The TreeView is merely the way the background work is going to status / progress itself to the user.
I have a set of methods that I want to run on not the main UI thread. They need to run in order. They are steps in a larger process. I could organize them into a hierarchy; that would make a nice tree structure.
Each of these methods will be represented by a node in the tree. I probably got the idea for this method of visualization from the old Sql Server DTS status panel. I still like that idea.
I want to know when each method finishes and it's outcome, and possibly a few textual statuses along the way. I also want a general mechanism that I can use to bubble up progress. I will use those to do an owner drawn progress bar in the TreeView on the node that corresponds to that method.
I've read up some on multi-threading, and also on the Task class, but don't really understand it fully. And I don't care if the solution uses that or not. But maybe that would be more elegant I don't know. It seems more straight forward than call backs but maybe you know better.
The Task Class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Serialization;
using System.Linq;
namespace DeveloperWorkbench.Nodes
{
public class Task : INode
{
public delegate void TaskStatusDelegate(Task sender, TaskStatus taskStatus);
public event ProgressDelegate ProgressChanged;
public event StatusDelegate Status;
public event TaskStatusDelegate TaskStatusChanged;
public Task()
{
_children = new List<Task>();
}
[XmlIgnore()]
public bool CanHaveChildren { get; private set; }
private List<Task> _children;
public List<Task> Children
{
get
{
_children.ForEach(x => x.Parent = this);
return _children;
}
set
{
_children = value;
_children.ForEach(x => x.Parent = this);
}
}
[XmlIgnore()]
public List<string> ChildTypes { get; private set; }
public string FullName { get; set; }
private float _maxProgress = 0;
[Browsable(false)]
[XmlIgnore()]
public float MaxProgress
{
get { return _maxProgress; }
set
{
_maxProgress = value;
RaiseProgress(this, Progress, MaxProgress);
}
}
private Delegate _method;
[Browsable(false)]
[XmlIgnore()]
public Delegate Method
{
get { return _method; }
set
{
if (_method == value) return;
_method = value;
Name = Method.Method.Name;
TypeName = Method.Method.ReflectedType.FullName;
}
}
private string _name;
[ReadOnly(true)]
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
//Method = GetMethodByName(???, _name);
FullName = ProperCaseToSpaces(_name);
}
}
[Browsable(false)]
[XmlIgnore()]
public INode Parent { get; set; }
private float _progress = 0;
[Browsable(false)]
[XmlIgnore()]
public float Progress
{
get { return _progress; }
set
{
_progress = value;
RaiseProgress(this, Progress, MaxProgress);
}
}
public List<KeyValuePair<string, object>> RelatedItems { get; set; }
private TaskStatus _taskStatus = TaskStatus.Created;
[Browsable(false)]
[XmlIgnore()]
public TaskStatus TaskStatus
{
get { return _taskStatus; }
set
{
_taskStatus = value;
TaskStatusChanged(this, _taskStatus);
}
}
[ReadOnly(true)]
public string TypeName { get; set; }
public bool Visited { get; set; }
public Task Add(Task child)
{
Children.Add(child);
child.Parent = this;
child.ProgressChanged += Child_Progress;
return child;
}
private void Done(System.Threading.Tasks.Task task)
{
TaskStatus = TaskStatus.RanToCompletion;
}
public void Execute()
{
Progress = 0;
TaskStatus = TaskStatus.Running;
var systemTask = new System.Threading.Tasks.Task((Action)Method);
systemTask.ContinueWith(Done);
systemTask.Start();
if (Parent != null)
systemTask.Wait();
}
private static string ProperCaseToSpaces(string text)
{
return Regex.Replace(text, #"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1");
}
public void RaiseProgress(INode sender, float progress = 0, float maxProgress = 100)
{
ProgressChanged(sender, progress, maxProgress);
}
public void RaiseStatus(string status = "Ready")
{
Status(status);
}
public void Refresh(bool force)
{
throw new NotImplementedException();
}
public void RefreshChildren(bool force, string childType = null)
{
throw new NotImplementedException();
}
public List<KeyValuePair<string, INode>> RefreshRelatedItems(bool force)
{
throw new NotImplementedException();
}
//Usage: myTask.SetMethod(() => MyMethod(0, 40));
public void SetMethod(Action method)
{
Method = method;
}
//Usage: myTask.SetMethod(() => MyFunction(myArgument));
public void SetMethod<T>(Func<T> function)
{
Method = function;
}
public void SetMethod(Object target)
{
if (target.GetType().FullName == TypeName)
Method = GetMethodByName(target, Name);
else
{
var name = Name;
SetMethod(() => FakeExecute(this));
Name = name;
TypeName = null;
}
foreach (var child in Children)
{
child.SetMethod(target);
}
}
public void Child_Progress(INode sender, float progress = 0, float maxProgress = 100)
{
MaxProgress = _children.Sum(x => x.MaxProgress);
Progress = _children.Sum(x => x.Progress);
}
public static Task Create<T>(Func<T> method, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(method);
return task;
}
public static Task Create(Action method, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(method);
return task;
}
public static Task Create(string methodName, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(() => FakeExecute(task));
task.Name = methodName;
task.TypeName = null;
return task;
}
private static Task InnerCreate(Task parent)
{
var task = new Task();
if (parent != null)
parent.Add(task);
return task;
}
public static Task CurrentTask(Task rootTask, int stackFrame = 1)
{
var taskMethodName = new StackFrame(stackFrame).GetMethod().Name;
return Find(rootTask, taskMethodName);
}
private static void FakeExecute(Task task)
{
foreach (Task child in task.Children)
{
child.MaxProgress = 100;
child.Progress = 0;
child.TaskStatus = TaskStatus.WaitingToRun;
}
foreach (Task child in task.Children)
{
child.Execute();
}
}
private static Task Find(Task task, string methodName)
{
return task.Method.Method.Name == methodName ?
task :
task.Children.Select(child => Find(child, methodName)).FirstOrDefault(found => found != null);
}
static Delegate GetMethodByName(object target, string methodName)
{
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
MethodInfo method = target.GetType().GetMethod(methodName, bindingFlags);
return method.ReturnType == typeof(void) ? Delegate.CreateDelegate(typeof(Action), target, method) : null;
}
}
}
The StatusList class:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using Retalix.R10.DeveloperWorkbench.Nodes;
using Retalix.R10.DeveloperWorkbench.UI.Helpers;
using Task = Retalix.R10.DeveloperWorkbench.Nodes.Task;
namespace DeveloperWorkbench.UI.Controls
{
public partial class StatusList : UserControl
{
// Import the SetWindowRgn function from the user32.DLL
// From the Unmanaged Code
[DllImport("user32.DLL", EntryPoint = "SetWindowRgn")]
private static extern int SetWindowRgn(int hWnd, int hRgn, int bRedraw);
[System.Runtime.InteropServices.DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern System.IntPtr CreateRoundRectRgn
(
int nLeftRect, // x-coordinate of upper-left corner
int nTopRect, // y-coordinate of upper-left corner
int nRightRect, // x-coordinate of lower-right corner
int nBottomRect, // y-coordinate of lower-right corner
int nWidthEllipse, // height of ellipse
int nHeightEllipse // width of ellipse
);
[System.Runtime.InteropServices.DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
private static extern bool DeleteObject(System.IntPtr hObject);
public StatusList()
{
InitializeComponent();
}
private TreeNode Add(TreeNodeCollection nodes, string text, string imageKey, object tag)
{
var treeNode = nodes.Add(tag.GetHashCode().ToString(), text);
treeNode.Tag = tag;
treeNode.ImageKey = imageKey;
treeNode.SelectedImageKey = imageKey;
tvTreeView.ExpandAll();
return treeNode;
}
public TreeNode Add(Task task)
{
var nodes = tvTreeView.Nodes;
if (task.Parent != null)
nodes = Find(task.Parent).Nodes;
task.TaskStatusChanged += Task_TaskStatusChanged;
task.ProgressChanged += Task_Progress;
var treeNode = Add(nodes, task.FullName, StatusIcon(task.TaskStatus), task);
foreach(var child in task.Children)
{
Add(child);
}
return treeNode;
}
private TreeNode Find(object tag)
{
var treeNodes = tvTreeView.Nodes.Find(tag.GetHashCode().ToString(), true);
if (treeNodes.Length > 0)
return treeNodes[0];
return null;
}
private string StatusIcon(System.Threading.Tasks.TaskStatus status)
{
switch (status)
{
case TaskStatus.Canceled:
case TaskStatus.Created:
case TaskStatus.Faulted:
case TaskStatus.RanToCompletion:
return status.ToString();
break;
case TaskStatus.Running:
case TaskStatus.WaitingForChildrenToComplete:
return TaskStatus.Running.ToString();
break;
default:
if (status.ToString().StartsWith("Waiting"))
return "Waiting";
break;
}
return "Created";
}
private void tvTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
var task = (Task) e.Node.Tag;
if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
//e.Graphics.DrawRectangle(SystemPens.ControlDark, e.Bounds.Left, e.Bounds.Top , e.Bounds.Width - 1, e.Bounds.Height - 1);
}
if(task.TaskStatus == TaskStatus.Running)
{
var borderBrush = new LinearGradientBrush(new Point(e.Bounds.Left + 1, e.Bounds.Top + 3), new Point(e.Bounds.Left + 1, e.Bounds.Bottom), Color.White, Color.FromArgb(200, Color.LightGray));
var borderRectangle = new Rectangle(e.Bounds.Left + 1, e.Bounds.Top + 3, e.Bounds.Width - 10, e.Bounds.Height - 6);
var borderGraphicsPath = RoundedRectangle.Create(borderRectangle);
e.Graphics.FillPath(borderBrush, borderGraphicsPath);
e.Graphics.DrawPath(Pens.DarkGray, borderGraphicsPath);
//e.Graphics.FillRectangle(borderBrush, borderRectangle);
//e.Graphics.DrawRectangle(pen, borderRectangle);
if (task.Progress > 0)
{
//pen.DashStyle = DashStyle.Dot;
var width = (task.Progress / task.MaxProgress) * (e.Bounds.Width - 11);
var progressRectangle = new Rectangle(e.Bounds.Left + 2, e.Bounds.Top + 4, (int)width, e.Bounds.Height - 7);
var progressGraphicsPath = RoundedRectangle.Create(progressRectangle, 5, RoundedRectangle.RectangleCorners.TopLeft | RoundedRectangle.RectangleCorners.BottomLeft);
//e.Graphics.DrawRectangle(pen, rectangle);
var progressBrush = new LinearGradientBrush(new Point(progressRectangle.Left, progressRectangle.Top - 1), new Point(progressRectangle.Left, progressRectangle.Bottom), Color.White, Color.LimeGreen);
e.Graphics.FillPath(progressBrush, progressGraphicsPath);
//e.Graphics.FillRectangle(progressLinearGradientBrush, progressRectangle);
//GraphicsPath path = RoundedRectangle.Create(rectangle);
//e.Graphics.DrawPath(Pens.Black, path);
//System.IntPtr ptrBorder = CreateRoundRectRgn(e.Bounds.Left, e.Bounds.Top, e.Bounds.Left + 50, e.Bounds.Bottom, 5, 5);
//try { SetWindowRgn(tvTreeView.Handle.ToInt32(), ptrBorder.ToInt32(), 1) ; }
//finally { DeleteObject(ptrBorder); }
}
}
var textSize = e.Graphics.MeasureString(task.Name, tvTreeView.Font);
var controlText = SystemBrushes.ControlText;
e.Graphics.DrawString(task.Name, tvTreeView.Font, controlText, e.Bounds.Left - 1, e.Bounds.Top + e.Bounds.Height / 2f - textSize.Height / 2f);
//if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
// controlText = SystemBrushes.HighlightText;
}
public void Task_Progress(Nodes.INode sender, float progress = 0, float maxProgress = 100)
{
if (IsDisposed) return;
if (InvokeRequired)
{
Invoke(new ProgressDelegate(Task_Progress), sender, progress, maxProgress);
}
else
{
if (tvTreeView.IsDisposed) return;
var treeNode = Find(sender);
if (treeNode != null)
{
tvTreeView.Invalidate(treeNode.Bounds);
}
}
}
public void Task_TaskStatusChanged(Task sender, TaskStatus taskStatus)
{
if (IsDisposed) return;
if (InvokeRequired)
{
Invoke(new Task.TaskStatusDelegate(Task_TaskStatusChanged), sender, taskStatus);
}
else
{
if (tvTreeView.IsDisposed) return;
var treeNode = Find(sender);
if (treeNode != null)
{
treeNode.ImageKey = StatusIcon(taskStatus);
treeNode.SelectedImageKey = treeNode.ImageKey;
}
}
}
}
}
And how it's used:
using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Task = Retalix.R10.DeveloperWorkbench.Nodes.Task;
namespace DeveloperWorkbench.UI
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
BuildTaskHierarchy();
}
private Task _rootTask;
public void BuildTaskHierarchy()
{
var roottaskXml = #"c:\temp\roottask.xml";
if (File.Exists(roottaskXml))
{
//method hierarchy can be deserialized...
_rootTask = (Task)Serialization.Deserialize(typeof(Task), roottaskXml);
_rootTask.SetMethod(target: this);
}
else
{
//...or constructed from scratch
_rootTask = Task.Create("Avert War With The Klingons");
Task.Create(GetToTheEnterprise, _rootTask);
var taskC = Task.Create("Kill General Chang", _rootTask);
Task.Create(FindThatThingsTailpipe, taskC);
Task.Create(TargetThatExplosionAndFire, taskC);
Task.Create(ThwartCampKhitomerAssassination, _rootTask);
Task.Create(ExplainToHighCommand, _rootTask);
Serialization.Serialize(_rootTask, roottaskXml);
}
statusList1.Add(_rootTask);
}
private void GetToTheEnterprise()
{
LongOp();
}
private void FindThatThingsTailpipe()
{
LongOp();
}
private void TargetThatExplosionAndFire()
{
LongOp();
}
private void ThwartCampKhitomerAssassination()
{
LongOp();
}
private void ExplainToHighCommand()
{
LongOp();
}
private void LongOp()
{
var task = Task.CurrentTask(_rootTask, 2);
task.MaxProgress = 100;
for (var i = 0; i <= 50; i++)
{
task.Progress = i*2;
Thread.Sleep(25);
}
}
private void button1_Click(object sender, EventArgs e)
{
_rootTask.Execute();
}
}
}
I'm just posting my progress. I have tested this in my actual application and it's working. I still need a convenience function to raise progress from within any method. I am still looking for feedback about how I can reduce the instrumentation required here. I want the least invasive strategy possible. Something that watches the call chain at run time would be an awesome addition.
The Progress class makes updating a UI with progress quite easy.
Just create a progress instance from within your UI; something that can take whatever information the background process currently has an update the UI appropriately:
Progress<Tuple<Operation, int>> progress = new Progress<Tuple<Operation, int>>();
progress.ProgressChanged += (_, info) =>
{
TreeView node = GetTreeviewFromOperation(info.Item1);
UpdateNodeWithProgress(node, info.Item2);
};
You can adjust that to whatever your circumstances are. Presumably the background process will have some sort of type that represents an operation, and you can map that back to the tree node that represents it. You could also pass in any other info you need to use to update the UI. If you have a lot of information to pass consider creating a new named type to represent it, rather than just using a Tuple as I did here.
Then just pass the progress to your background process, whatever that is (it could be a new thread, a task, the callback of an asynchronous method, or whatever).
//this is the work to do in the background
public static void DoWork(IProgress<Tuple<Operation, int>> progress)
{
Thread.Sleep(1000); //placeholder for real work
progress.Report(something, 50);
}
//start that work in a new task; call from the UI thread
//right after creating the `Progress` instance
Task.Run(()=> DoWork(progress));
If you don't have .NET 4.5 you can create your own version of this class rather easily:
public interface IProgress<T>
{
void Report(T data);
}
public class Progress<T> : IProgress<T>
{
SynchronizationContext context;
public Progress()
{
context = SynchronizationContext.Current
?? new SynchronizationContext();
}
public Progress(Action<T> action)
: this()
{
ProgressReported += action;
}
public event Action<T> ProgressReported;
void IProgress<T>.Report(T data)
{
var action = ProgressReported;
if (action != null)
{
context.Post(arg => action((T)arg), data);
}
}
}
Read up on the BackgroundWorker class. It's an old but very simple way to do background work without going into the complexities of threading.
All you got to do is create one, handle its DoWork event to perform your logic (which will run in the background) and pass progress back to the main ui thread through its ReportProgress function which you will then handle to update the ui of your tree however you like.
The best option to update UI is to leave the synchronisation responsibilities to .NET itself. Make use of async and await that helps to switch to UI thread from background thread when the Task is completed.
This library solves the exact purpose that you require.
Take a look at the examples WPF and Blazor here.
NUGET package here.

Asynchronously add value to Bindinglist/Cross-threading & Locking issue

I have a BindingList databound to a datgridview. I'm using it to keep track of some real-time prices. The method 'update(Quote quote)' is called multiple times a second by various threads. If the datagridview doesn't contain the Quote, it is added. If it does, the values of the quote are updated. I don't want the same quote to appear in the BindingList (or on the GUI) twice, so I tried to put a lock around the operation that checks whether the value is in the list or not. It doesn't work! What am I doing wrong? I've tried two different ways of locking, and am locking on a String object rather than just an object. The problem is definitely in the BeginInvoke(new MethodInvoker(delegate() { activeQuotes.Insert(0, quote); })); call (which is probably taking some time), but if I make that synchronous, the 'add' method throws a 'cross-threading' error. . . What can I do to avoid the cross-threading error, but ensure that the lock works also??
public BindingList<Quote> activeQuotes = new BindingList<Quote>();
object lockObject = "lockObject";
dataGridViewActive.DataSource = activeQuotes;
public void update(Quote quote)
{
//lock (lockObject)
if(Monitor.TryEnter(lockObject))
{
try
{
if (!activeQuotes.Contains(quote))
{
try
{
activeQuotes.Add(quote);
AddQuote(quote);
}
catch (Exception ex)
{
Console.WriteLine("Datagridview!!!!!!");
}
}
else
{
int index = activeQuotes.IndexOf(quote);
activeQuotes[index].Bid = quote.Bid;
activeQuotes[index].Ask = quote.Ask;
activeQuotes[index].Mid = quote.Mid;
activeQuotes[index].Spread = quote.Spread;
activeQuotes[index].Timestamp = quote.Timestamp;
}
finally
{
Monitor.Exit(lockObject);
}
}
private void AddQuote(Quote quote)
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { activeQuotes.Insert(0, quote); }));
BeginInvoke(new MethodInvoker(delegate() { dataGridViewActive.Refresh(); }));
BeginInvoke(new MethodInvoker(delegate() { dataGridViewActive.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); }));
}
else
{
activeQuotes.Add(quote);
dataGridViewActive.Refresh();
dataGridViewActive.AutoResizeColumns (DataGridViewAutoSizeColumnsMode.AllCells);
}
}
I'd appreciate any help at all on this.
Thanks.
this code I wrote before will do
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
BindingListInvoked<Name> names;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
names = new BindingListInvoked<Name>(dataGridView1);
dataGridView1.DataSource = names;
new Thread(() => names.Add(new Name() { FirstName = "Larry", LastName = "Lan" })).Start();
new Thread(() => names.Add(new Name() { FirstName = "Jessie", LastName = "Feng" })).Start();
}
}
public class BindingListInvoked<T> : BindingList<T>
{
public BindingListInvoked() { }
private ISynchronizeInvoke _invoke;
public BindingListInvoked(ISynchronizeInvoke invoke) { _invoke = invoke; }
public BindingListInvoked(IList<T> items) { this.DataSource = items; }
delegate void ListChangedDelegate(ListChangedEventArgs e);
protected override void OnListChanged(ListChangedEventArgs e)
{
if ((_invoke != null) && (_invoke.InvokeRequired))
{
IAsyncResult ar = _invoke.BeginInvoke(new ListChangedDelegate(base.OnListChanged), new object[] { e });
}
else
{
base.OnListChanged(e);
}
}
public IList<T> DataSource
{
get
{
return this;
}
set
{
if (value != null)
{
this.ClearItems();
RaiseListChangedEvents = false;
foreach (T item in value)
{
this.Add(item);
}
RaiseListChangedEvents = true;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
}
}
}
public class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
I think you should change your BeginInvoke to just Invoke. You need to get it on the UI thread, not begin an async operation. Otherwise your lock could get released before the BeginInvoke target gets invoked because control is returned immediately upon calling BeginInvoke. Calling Invoke will block that thread on that call until the Invoke target completes then return control back to your thread, which will ensure the lock is kept.
Also, have you considered using a lock block instead of Monitor method calls? It's basically the same thing but prevents you from needing the try/finally. I don't see that you're using any retry or benefit from the TryEnter, but perhaps the code sample doesn't demonstrate that.

Categories