I have read through a few threads here, but have not come to any conclusion.
An asynchronous method in another class is to be executed by button click.
In this method the current state should be added again and again in a text field of the form:
public interface MainForm {
string LogText { get; set; }
}
public partial class KatalogForm : Form, MainForm {
public string LogText {
get { return rtbxLog.Text; }
set { rtbxLog.Text += value; }
}
private void btnCreateCatalogues_Click(object sender, EventArgs e) {
Task.Run(() => catalogues.Create());
}
}
Excerpt from the second class:
private static MainForm mainForm;
public async void Create() {
//Stuff
//Update TextBox
}
Since the method is asynchronous, I can't access the textbox directly here.
I know that I have to work with Invoke here, but I can't implement this properly.
What is the best way to solve this?
Usually, when I have to asynchronously update a TextBox with data from external sources (ex: an operations log) I use a queue and a timer (Windows.Forms).
The method called by external objects adds the new data.
The timer clears pending data at a reasonable rate and updates the text box.
The advantages are:
Fast requests: The update method invoked by external sources only add items to the queue.
Thread safe: The text box is only updated within its own UI thread, so no cross-thread errors can occur.
Less allocations: No Task or BeginInvoke calls are required, avoiding allocation of temporary objects.
Few UI updates: The items are applied to the text box as batches (multiple items at the same time) based on the timer frequency.
First-in/First-out: No risk of overlap of items due to asynchronous operations scheduled and executed in the wrong order.
See the sample class (LogBox user control) below, split into LogBox.cs and LogBox.Designer.cs:
Other objects would call Log method.
The default rate is 100ms; that is, 10 times a second (for a human reader should be enough).
Sample code (LogBox.cs):
using System;
using System.Collections.Concurrent;
using System.Windows.Forms;
namespace SAMPLE
{
[Docking(DockingBehavior.Ask)]
public partial class LogBox : UserControl
{
private readonly ConcurrentQueue<string> PendingLog = new ConcurrentQueue<string>();
public LogBox()
{
InitializeComponent();
}
private void tmrLog_Tick(object sender, EventArgs e) { this.ProcessPendingLog(); }
private void ProcessPendingLog()
{
if (!this.Disposing && !this.IsDisposed && this.IsHandleCreated)
try
{
if (!this.PendingLog.IsEmpty)
{
string item;
while (this.PendingLog.TryDequeue(out item))
{
txtLog.AppendText(item);
}
}
}
catch (Exception ex) { /* ... */ }
}
public void Log(string text) { this.PendingLog.Enqueue(text); }
}
}
Sample code (LogBox.Designer.cs):
namespace SAMPLE
{
partial class LogBox
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.tmrLog = new System.Windows.Forms.Timer(this.components);
this.txtLog = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// tmrLog
//
this.tmrLog.Enabled = true;
this.tmrLog.Tick += new System.EventHandler(this.tmrLog_Tick);
//
// txtLog
//
this.txtLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtLog.Location = new System.Drawing.Point(0, 0);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.txtLog.Size = new System.Drawing.Size(200, 200);
this.txtLog.TabIndex = 0;
//
// LogBox
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.txtLog);
this.Name = "LogBox";
this.Size = new System.Drawing.Size(200, 200);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Timer tmrLog;
private System.Windows.Forms.TextBox txtLog;
}
}
I would recommend use IProgress<string> for catalogues.Create()
Task.Run(async () =>
{
var createProgress = new Progress<string>(ReportProgress);
await catalogues.Create(createProgress);
});
void ReportProgress(string reportMessage)
{
//update log here
}
usage inside Create
async Task Create(IProgress<string> progress)
{
foreach (var category in categories)
{
// some staff
progress.Report($"{category} - completed");
}
}
IProgress example
Related
So, I was creating a windows service and i was following this video to setup the windows service.
https://www.youtube.com/watch?v=tAF9krJu3cs
So, I have a Model, I get the Model in the controller so I can build what I want to send, then, I call that Controller on the Service. After that, I created the Installer. Problem: I have 3 and i was able to create them adding: [RunInstaller(true)] in the beggining of every service. After that, I was saying that they were not manual and "prepraring" them for the errors, starting them, and, after that I started them. But, I turned on one of them, the other turns off, and so on. I was able to turn the 3 on after a while (maybe this was a bug) but they all stopped working again..... My code:
ProjectInstaller:
enter image description here
ProjectInstaller.Designer.cs :
{
partial class ProjectInstaller
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.AccountService = new System.ServiceProcess.ServiceInstaller();
this.FinInfoPrevYearService = new System.ServiceProcess.ServiceInstaller();
this.GetFinancialInfoService = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalService;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
this.serviceProcessInstaller1.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.serviceProcessInstaller1_AfterInstall);
//
// AccountService
//
this.AccountService.ServiceName = "AccountService";
this.AccountService.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.AccountService_AfterInstall);
//
// FinInfoPrevYearService
//
this.FinInfoPrevYearService.ServiceName = "FinInfoPrevYearService";
this.FinInfoPrevYearService.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.FinInfoPrevYearService_AfterInstall);
//
// GetFinancialInfoService
//
this.GetFinancialInfoService.ServiceName = "GetFinancialInfoService";
this.GetFinancialInfoService.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.GetFinancialInfoService_AfterInstall);
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1,
this.AccountService,
this.FinInfoPrevYearService,
this.GetFinancialInfoService});
}
#endregion
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller AccountService;
private System.ServiceProcess.ServiceInstaller FinInfoPrevYearService;
private System.ServiceProcess.ServiceInstaller GetFinancialInfoService;
}
}
ProjectInstaller.cs:
{
[RunInstaller(true)]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e)
{
}
private void AccountService_AfterInstall(object sender, InstallEventArgs e)
{
}
private void GetFinancialInfoService_AfterInstall(object sender, InstallEventArgs e)
{
}
private void FinInfoPrevYearService_AfterInstall(object sender, InstallEventArgs e)
{
}
}
}
One of the services:
enter image description here
Please help me guys.. I dont know what to do anymore...
I tried to set Text property of TextBox from another thread. I got this exception below;
"Cross-thread operation not valid: Control 'recTpcTxt' accessed from a thread other than the thread it was created on."
Then, I used BackgroundWorker to solve this issue. However, I faced with the same exception message.
EDIT[1]:
Actually, I take a guide myself this link ; https://msdn.microsoft.com/en-us/library/ms171728(v=vs.110).aspx. I can solve my problem by using invokeproperty. However, I cannot solve my problem with backgroundworker.
Is there something wrong in my solution? How do I fix my solution to set some property of UI variable?
EDIT[2]: More code to clarify the issue;
MqttManager.cs;
public partial class MqttManager : Form
{
MqttHandler mqttHandler = new MqttHandler();
public static MqttManager managerInst;
public MqttManager()
{
InitializeComponent();
managerInst = this;
...
}
...
private BackgroundWorker backgroundWorker;
public void NotifyUIForRecMsg(string topic, string message)
{
object[] objArr = { topic, message };
this.backgroundWorker.RunWorkerAsync(objArr);
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(5000);
e.Result = e.Argument;
}
private void backgroundWorker_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
object[] res = (object[])e.Result;
this.recTpcTxt.Text = (String)res[0];
}
}
MqttManager.Design.cs;
partial class MqttManager
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
...
this.backgroundWorker = new System.ComponentModel.BackgroundWorker();
this.backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker_DoWork);
this.backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker_RunWorkerCompleted);
}
#endregion
...
}
MqttHandler.cs;
class MqttHandler
{
MqttClient client;
...
/// <summary>
/// Publish received event handler.
/// </summary>
private void client_MqttMsgPublishReceived(Object sender, MqttMsgPublishEventArgs e)
{
MqttManager.managerInst.NotifyUIForRecMsg(e.Topic, Encoding.UTF8.GetString(e.Message));
}
}
check this:
https://msdn.microsoft.com/en-us/library/ms171728(v=vs.110).aspx
Basically, to set a control propertiy you have to be in the same UI thread.
This simple solution move the call to textbox1.Text = someText in the UI thread
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
also, you can use textBox1.BeginInvoke instead of Invoke: it will run in UI thread, without locking the caller thread waiting for SetText delegate to be completed
[Edit] to do it in your backgroundWorker:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
object[] arg = (object[])e.Argument;
SetTextToTextBox(recTpcTxt, (string)arg[0]);
SetTextToTextBox(recMsgTxt, (string)arg[1]);
}
private void SetTextToTextBox(TextBox toSet, string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (toSet.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
toSet.Invoke(d, new object[] { text });
}
else
{
toSet.Text = text;
}
}
[Edit 2]
To properly use backgroundworker
Register for events DoWork and RunWorkerCompleted
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
Before exiting backgroundWorker1_DoWork, set result property of eventArgs, and read them in backgroundWorker1_RunWorkerCompleted
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(5000);
e.Result = new string[] { "one", "two" };
}
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
string[] res = (string[])e.Result;
this.textBox1.Text = res[0];
}
I want to display an animated loading form while executing some code in the main form. The animated form is used only to show the user that an operation is executing and I want to close it once the operation finishes. The code that I'm using is:
public partial class Form_main_admin : Form
{
private Thread loadingThread;
private string loadingText;
public Form_main_admin()
{
InitializeComponent();
}
private void main_tabControl_SelectedIndexChanged(object sender, EventArgs e)
{
switch (main_tabControl.SelectedIndex)
{
case 0:
// ...
break;
case 1:
showLoadingForm("Loading");
// Load a datagridview (load data, adjust column widths) in Form_main_admin
closeLoadingForm();
break;
}
}
private void showLoadingForm(string text)
{
loadingText = text;
loadingThread = new Thread(new ThreadStart(openLoadingForm));
loadingThread.Start();
}
private void openLoadingForm()
{
try
{
Form_loading loadingForm = new Form_loading(loadingText);
loadingForm.ShowDialog();
}
catch
{
Thread.ResetAbort();
}
}
private void closeLoadingForm()
{
try
{
loadingThread.Abort();
}
catch
{
Thread.ResetAbort();
}
}
}
The problem is that I get a "Thread was being aborted" exception when I quickly change between tabs (see image in link below).
http://postimg.org/image/bvre2bmi5/
I do not want the user to see this exception if he chages tabs too fast. After reading other posts on this forum I realized that my implementation is not recommended. Could someone please show me how to properly implement this functionality?
If you need an animated progress form, try to use BackgroundWorker class to perform loading in an additional thread:
public partial class MainForm : Form
{
/// <summary>
/// Some progress form
/// </summary>
WaitForm waitForm = new WaitForm();
/// <summary>
/// https://msdn.microsoft.com/library/cc221403(v=vs.95).aspx
/// </summary>
BackgroundWorker worker = new BackgroundWorker();
public MainForm()
{
InitializeComponent();
worker.DoWork += (sender, args) => PerformReading();
worker.RunWorkerCompleted += (sender, args) => ReadingCompleted();
}
/// <summary>
/// This method will be executed in an additional thread
/// </summary>
void PerformReading()
{
//some long operation here
Thread.Sleep(5000);
}
/// <summary>
/// This method will be executed in a main thread after BackgroundWorker has finished
/// </summary>
void ReadingCompleted()
{
waitForm.Close();
}
private void button1_Click(object sender, EventArgs e)
{
//Run reading in an additional thread
worker.RunWorkerAsync();
//Show progress form in a main thread
waitForm.ShowDialog();
}
}
I have a number of classes that do stuff, typically step through a recordset and call a webservice or two for each record.
At the moment this all runs in the GUI thread and hangs painting. First thought was to use a BackgroundWorker and implement a nice progress bar, handle errors, completion etc. All the nice things a Background worker enables.
As soon as the code hit the screen it started to smell. I was writing a lot of the background worker into each class, repeating most of the ProcessRows method in a bw_DoWork method and thinking there should be a better way, and it's probably already been done.
Before I go ahead and reinvent the wheel is there a pattern or implementation for a class that seperates out the background worker? It would take classes that implement an interface such as ibackgroundable, but the classes could still be run standalone, and would require minimal change to implement the interface.
Edit: A simplified example requested by #Henk:
I have:
private void buttonUnlockCalls_Click(object sender, EventArgs e)
{
UnlockCalls unlockCalls = new UnlockCalls();
unlockCalls.MaxRowsToProcess = 1000;
int processedRows = unlockCalls.ProcessRows();
this.textProcessedRows.text = processedRows.ToString();
}
I think I want:
private void buttonUnlockCalls_Click(object sender, EventArgs e)
{
UnlockCalls unlockCalls = new UnlockCalls();
unlockCalls.MaxRowsToProcess = 1000;
PushToBackground pushToBackground = new PushToBackground(unlockCalls)
pushToBackground.GetReturnValue = pushToBackground_GetReturnValue;
pushToBackground.DoWork();
}
private void pushToBackground_GetReturnValue(object sender, EventArgs e)
{
int processedRows = e.processedRows;
this.textProcessedRows.text = processedRows.ToString();
}
I could go ahead and do this, but don't want to reinvent.
The answer I'm looking for would along the lines of "Yes, Joe did a good implementation of that (here)" or "That's a Proxy Widget pattern, go read about it (here)"
Each operation needs to implement the following interface:
/// <summary>
/// Allows progress to be monitored on a multi step operation
/// </summary>
interface ISteppedOperation
{
/// <summary>
/// Move to the next item to be processed.
/// </summary>
/// <returns>False if no more items</returns>
bool MoveNext();
/// <summary>
/// Processes the current item
/// </summary>
void ProcessCurrent();
int StepCount { get; }
int CurrentStep { get; }
}
This seperates the enumeration of the steps from the processing.
Here is a sample operation:
class SampleOperation : ISteppedOperation
{
private int maxSteps = 100;
//// The basic way of doing work that I want to monitor
//public void DoSteppedWork()
//{
// for (int currentStep = 0; currentStep < maxSteps; currentStep++)
// {
// System.Threading.Thread.Sleep(100);
// }
//}
// The same thing broken down to implement ISteppedOperation
private int currentStep = 0; // before the first step
public bool MoveNext()
{
if (currentStep == maxSteps)
return false;
else
{
currentStep++;
return true;
}
}
public void ProcessCurrent()
{
System.Threading.Thread.Sleep(100);
}
public int StepCount
{
get { return maxSteps; }
}
public int CurrentStep
{
get { return currentStep; }
}
// Re-implement the original method so it can still be run synchronously
public void DoSteppedWork()
{
while (MoveNext())
ProcessCurrent();
}
}
This can be called from the form like this:
private void BackgroundWorkerButton_Click(object sender, EventArgs eventArgs)
{
var operation = new SampleOperation();
BackgroundWorkerButton.Enabled = false;
BackgroundOperation(operation, (s, e) =>
{
BackgroundWorkerButton.Enabled = true;
});
}
private void BackgroundOperation(ISteppedOperation operation, RunWorkerCompletedEventHandler runWorkerCompleted)
{
var backgroundWorker = new BackgroundWorker();
backgroundWorker.RunWorkerCompleted += runWorkerCompleted;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler((s, e) =>
{
while (operation.MoveNext())
{
operation.ProcessCurrent();
int percentProgress = (100 * operation.CurrentStep) / operation.StepCount;
backgroundWorker.ReportProgress(percentProgress);
if (backgroundWorker.CancellationPending) break;
}
});
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler((s, e) =>
{
var progressChangedEventArgs = e as ProgressChangedEventArgs;
this.progressBar1.Value = progressChangedEventArgs.ProgressPercentage;
});
backgroundWorker.RunWorkerAsync();
}
I haven't done it yet but I'll be moving BackgroundOperation() into a class of its own and implementing the method to cancel the operation.
I would put my non-UI code into a new class and use a Thread (not background worker). To show progress, have the new class fire events back to the UI and use Dispatcher.Invoke to update the UI.
There is a bit of coding in this, but it is cleaner and works. And is more maintainable than using background worker (which is only really intended for small tasks).
So I have
public class Form1 : Form {}
and
class Updater {}
And I have textBox1 on Form1, along with many other controls...
So here is my dilemma: I have a while(true) {} loop in Updater, and I couldn't stick it in the Form1 class, because it was preventing the form from loading. And I need to update a multi-lined textbox (textBox1) on Form1, from Updater. Updater is a TCP client, and when it receives information I need it to += its information into the textbox.. But how do I access the textbox from a thread different from the one it was created in?
Edit: I'm looking for code examples, please.
Why don't you declare an event in Updater class? Then you can raise this event when you get data from TCP.
public class Updater
{
public delegate void DataReceivedEventHandler(object sender,DataEventArgs e);
public event DataReceivedEventHandler DataReceived = delegate { };
public void ReadData()
{
//here you will get data from what ever you like
//upon recipt of data you will raise the event.
//THIS LOOP IS FOR TESTING ONLY
for (var i = 1; i < 101; i++)
{
//PASS REAL DATA TO new DataEventArgs
DataReceived(this, new DataEventArgs("Event " + i));
Thread.Sleep(500);
}
}
}
public class DataEventArgs : EventArgs
{
public string Data { get; set; }
public DataEventArgs(string data) : base()
{
Data = data;
}
}
In you form:
//you will setup "Updater" in some else way. I've written this function
//which I call on a button click for testing
private void Init()
{
var u = new Updater();
u.DataReceived += delegate(object sender, DataEventArgs e)
{ SetTextboxText(e.Data); };
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += delegate(object sender, DoWorkEventArgs e)
{ ((Updater)e.Argument).ReadData(); };
bw.RunWorkerAsync(u);
}
private void SetTextboxText(string s)
{
if (TEXT_BOX.InvokeRequired)
{
//This techniques is from answer by #sinperX1
BeginInvoke((MethodInvoker)(() => { SetTextboxText(s); }));
return;
}
TEXT_BOX.Text += Environment.NewLine + s;
}
If Form1 has a reference to Updater then you can put an event on the Updater Class that Form1 can subscribe to. When Updater has data (or whatever reason it needs to update the form) it sets the event, the form catches the event and updates the textbox.
Example:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Updater textboxUpdater = new Updater();
textboxUpdater.Updated += s => {textBox1.Text = s;};
}
}
public class Updater
{
public delegate void UpdateEventHandler(string eventName);
public event UpdateEventHandler Updated = delegate { };
private bool needUpdating;
public void Process()
{
while (true)
{
//Processing
if (needUpdating)
{
Updated("something");
}
}
}
}
Cross-threading is caused when a thread is used to access a control that did not create the control. To get around it you Invoke.
http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
Example:
/// <summary>
/// This is a thread safe operation.
/// </summary>
/// <param name="text"></param>
public void SetTextBoxText(string text)
{
if (InvokeRequired)
{
Invoke((MethodInvoker)delegate { SetText(text); });
return;
}
// To get to this line the proper thread was used (by invoking)
myTextBoxt.Text += text;
}
/// <summary>
/// This is an alternative way. It uses a Lambda and BeginInvoke
/// which does not block the thread.
/// </summary>
/// <param name="text"></param>
public void SetTextBoxText(string text)
{
if (InvokeRequired)
{
BeginInvoke((MethodInvoker)(() => { SetText(text); }));
return;
}
// To get to this line the proper thread was used (by invoking)
myTextBox.Text += text;
}