Backgroundworker access via sender parameter - c#

There is a Microsoft Docs example which shows how to use a BackgroundWorker. And in the sample code is this comment, followed by an access to the BackgroundWorker via a sender parameter:
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
What error or behavior is being avoided here? And is this always necessary? For example, if I have a background worker that I create apart from a form, will this still be a good practice?
Full example from the link reproduced here for convenience:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace BackgroundWorkerExample
{
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
// Extract the argument.
int arg = (int)e.Argument;
// Start the time-consuming operation.
e.Result = TimeConsumingOperation(bw, arg);
// If the operation was canceled by the user,
// set the DoWorkEventArgs.Cancel property to true.
if (bw.CancellationPending)
{
e.Cancel = true;
}
}
// This event handler demonstrates how to interpret
// the outcome of the asynchronous operation implemented
// in the DoWork event handler.
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// The user canceled the operation.
MessageBox.Show("Operation was canceled");
}
else if (e.Error != null)
{
// There was an error during the operation.
string msg = String.Format("An error occurred: {0}", e.Error.Message);
MessageBox.Show(msg);
}
else
{
// The operation completed normally.
string msg = String.Format("Result = {0}", e.Result);
MessageBox.Show(msg);
}
}
// This method models an operation that may take a long time
// to run. It can be cancelled, it can raise an exception,
// or it can exit normally and return a result. These outcomes
// are chosen randomly.
private int TimeConsumingOperation(
BackgroundWorker bw,
int sleepPeriod )
{
int result = 0;
Random rand = new Random();
while (!bw.CancellationPending)
{
bool exit = false;
switch (rand.Next(3))
{
// Raise an exception.
case 0:
{
throw new Exception("An error condition occurred.");
break;
}
// Sleep for the number of milliseconds
// specified by the sleepPeriod parameter.
case 1:
{
Thread.Sleep(sleepPeriod);
break;
}
// Exit and return normally.
case 2:
{
result = 23;
exit = true;
break;
}
default:
{
break;
}
}
if( exit )
{
break;
}
}
return result;
}
private void startBtn_Click(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync(2000);
}
private void cancelBtn_Click(object sender, EventArgs e)
{
this.backgroundWorker1.CancelAsync();
}
/// <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.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
this.startBtn = new System.Windows.Forms.Button();
this.cancelBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// backgroundWorker1
//
this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//
// startBtn
//
this.startBtn.Location = new System.Drawing.Point(12, 12);
this.startBtn.Name = "startBtn";
this.startBtn.Size = new System.Drawing.Size(75, 23);
this.startBtn.TabIndex = 0;
this.startBtn.Text = "Start";
this.startBtn.Click += new System.EventHandler(this.startBtn_Click);
//
// cancelBtn
//
this.cancelBtn.Location = new System.Drawing.Point(94, 11);
this.cancelBtn.Name = "cancelBtn";
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
this.cancelBtn.TabIndex = 1;
this.cancelBtn.Text = "Cancel";
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(183, 49);
this.Controls.Add(this.cancelBtn);
this.Controls.Add(this.startBtn);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.ComponentModel.BackgroundWorker backgroundWorker1;
private System.Windows.Forms.Button startBtn;
private System.Windows.Forms.Button cancelBtn;
}
public class Program
{
private Program()
{
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
}
}

As #Hans Passant pointed out, sender and this.backgroundworker1 both reference the same object, and so do the same thing in the example. The reason for the warning is so if a future developer edits the backgroundWorker1_DoWork method to provide a different sender, sender will still work as intended, while this.backgroundworker1 might be accessing a backgroundworker which is no longer related to the method.

Related

Not creating new Backgroundworker instance - C#

I am creating a progress form that utilizes a backgroundworker to run the process. It runs okay the first time the form is displayed, but after that I get the error
Additional information: This operation has already had OperationCompleted
called on it and further calls are illegal.
when I try to call the TheBackgroundworker.ReportProgress() method.
I am confused, because I am creating the progress form in a using block like this:
using (ProgressForm FPProgForm = new ProgressForm(TheUI))
{
FPProgForm.ShowDialog();
if (FPProgForm.DialogResult == DialogResult.OK)
{
// display results screen
}
}
And in the FPProgForm constructor, I am creating a new BackgroundWorker()
TheBackgroundworker = new BackgroundWorker();
So the BackGroundWorker should be brand new every time I create a new dialog.
Update: On request, here is the entire progress form class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FPDWF
{
public partial class ProgressForm : Form
{
public delegate void RunFunctionDelegate();
RunFunctionDelegate FuncToRun { get; } // function to be run
FPDesktopWFUI TheUI { get; }
BackgroundWorker TheBackgroundworker; // for internal use only, like a viagra demo
public ProgressForm(RunFunctionDelegate funcToRun, FPDesktopWFUI theUI)
{
InitializeComponent();
FuncToRun = funcToRun;
TheUI = theUI;
TheBackgroundworker = new BackgroundWorker();
InitializeBackgroundWorker();
// subscription to event stuff here: http://stackoverflow.com/questions/14871238/report-progress-backgroundworker-from-different-class-c-sharp
TheUI.OnProgressUpdate += FPProgUpdate;
}
// Set up the BackgroundWorker object by
// attaching event handlers.
private void InitializeBackgroundWorker()
{
// background worker stuff here: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
TheBackgroundworker.DoWork +=
new DoWorkEventHandler(TheBackgroundworker_DoWork);
TheBackgroundworker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(TheBackgroundworker_RunWorkerCompleted);
TheBackgroundworker.ProgressChanged +=
new ProgressChangedEventHandler(TheBackgroundworker_ProgressChanged);
TheBackgroundworker.WorkerReportsProgress = true;
TheBackgroundworker.WorkerSupportsCancellation = true;
}
private void ProgressForm_Load(object sender, EventArgs e)
{
// progress bar stuff here: http://stackoverflow.com/questions/12126889/how-to-use-winforms-progress-bar
ui_progbar.Maximum = 100;
ui_progbar.Step = 1;
ui_progbar.Value = 0;
TheBackgroundworker.RunWorkerAsync();
}
private void ui_cancelbutton_Click(object sender, EventArgs e)
{
if (TheBackgroundworker.WorkerSupportsCancellation == true)
{
// Cancel the asynchronous operation.
TheBackgroundworker.CancelAsync(); // there really is no purpose to this as i can just set the contRunning flag I think
TheUI.contRunning = false; // i think this thread safe due to 'volatile flag', https://msdn.microsoft.com/en-us/library/7a2f3ay4(v=vs.100).aspx
resultLabel.Text = "Cancelling...";
}
}
// This event handler is where the time-consuming work is done.
private void TheBackgroundworker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
FuncToRun();
}
// This event handler updates the progress.
private void TheBackgroundworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// something to do here?
}
// This event handler deals with the results of the background operation.
private void TheBackgroundworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (TheBackgroundworker.CancellationPending == true) // if (e.Cancelled == true)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
else if (e.Error != null)
{
this.DialogResult = DialogResult.Abort;
resultLabel.Text = "Error: " + e.Error.Message;
ui_viewres_btn.Text = "Close";
ui_viewres_btn.Enabled = true;
}
else
{
this.DialogResult = DialogResult.OK;
ui_viewres_btn.Enabled = true;
}
}
private void FPProgUpdate(string progText, double prog)
{
// utilizing this: http://stackoverflow.com/a/14871753/3661120
int intProg = Convert.ToInt32(prog * 100);
if (!TheBackgroundworker.CancellationPending)
{
TheBackgroundworker.ReportProgress(intProg); // doesn't really do anything at this point, but whatev
base.Invoke((Action)delegate
{
resultLabel.Text = progText;
ui_progbar.Value = intProg;
});
}
}
private void ui_viewres_btn_Click(object sender, EventArgs e)
{
this.Close(); // closes the window
}
}
}
Update 2: even when I remove the offending TheBackgroundworker.ReportProgress(intProg); line, I am still getting this error:
Additional information: Invoke or BeginInvoke cannot be called on a
control until the window handle has been created.
You retrieve this error because you are subscribing to this event:
TheUI.OnProgressUpdate += FPProgUpdate;
Therefore FPProgUpdate calls ReportProgress() multiple times.
As you have already noticed, the following like is not necessary and you can remove it:
TheBackgroundworker.ReportProgress(intProg);
Thanks to Marc for the help on this. The solution was that I needed to unsubscribe FPProgUpdate from the TheUI.OnProgressUpdate event in the disposal method, which I had to override:
protected override void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
if (components != null)
{
components.Dispose();
}
// Dispose stuff here
TheUI.OnProgressUpdate -= FPProgUpdate;
}
disposed = true;
base.Dispose(disposing);
}
The disposal does not automatically unsubscribe, it seems like.
TheBackgroundworker.ReportProgress should only be called from inside of the thread that is executing DoWork. From your code it looks like FPProgUpdate contains a ReportProgress and is being called from some thread other than the thread that started DoWork.

JumpList in C# application (recent files)

At the moment I am developing an application and want to add a Windows (7) JumpList. I followed several tutorials and studied documentation, but I can't figure out how to get the job done. In short: I want a recent list of last choosen files. So after closing the app, the user can easily open a recent file with my application. I already implemented some file association mechanism.
Is it possible to share some code/ tutorial how I can solve above problem?
Thank you in advance!
*I already tried the next few projects/ tutorials:
http://www.codeproject.com/Articles/103913/How-to-Create-a-Custom-Jumplist-with-Custom-Events
http://channel9.msdn.com/coding4fun/articles/Windows-7-Jump-Lists
http://csharp-tricks-en.blogspot.nl/2011/10/create-jumplist-using-c.html
*The code of Coding 4 Fun works, but I don't know how to develop a recent file list.
You can check out this article. Instead of showing result in WPF you need to show it in jumplist.
Why dont you try storing the recently opened file names in a database or an xml file and read it to set the jumplist. For eg.
private void ReportUsage()
{
XmlDocument myXml = new XmlDocument();
myXml.Load(historyXml);
string list = historyXml;
jumpList.ClearAllUserTasks();
foreach (XmlElement el in myXml.DocumentElement.ChildNodes)
{
string s = el.GetAttribute("url");
JumpListLink jll = new JumpListLink(Assembly.GetEntryAssembly().Location, s);
jll.IconReference = new IconReference(Path.Combine("C:\\Program Files\\ACS Digital Media\\TOC WPF Browser\\Icon1.ico"), 0);
jll.Arguments = el.GetAttribute("url");
jumpList.AddUserTasks(jll);
}
jumpList.Refresh();
}
Or a beginners solution will be retain all the file paths into a Queue of given maximum capacity and adding them at run-time into a menuItem. Sorry I didnt have time to write the whole code.
As it is described in your second article, your application must be registered as a handler for targeted file extension, otherwise the recent category for your Jumplist won't show up. You can find more details about file association registration there.
You can either manually register your application but you will need admin right so it is not recommended, or create a setup project for your application like described in the coding 4 fun article, or you can let the user associate the file extension.
Here is a sample which works for me under Windows Seven without registration, by just right clicking the text file I want to load and choosing "Open With" and browse to my application.
The sample require Windows API Code Pack
public partial class Form1 : Form
{
[STAThread]
static void Main(string[] args)
{
var file = args != null && args.Length > 0 ? args[0] : string.Empty;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(file));
}
public Form1()
: this(string.Empty)
{
}
public Form1(string file)
{
InitializeComponent();
Open(file);
}
[DllImport("user32.dll")]
private static extern uint RegisterWindowMessage(string message);
private uint wmTBC;
/// <summary>
/// Registers the window message for notification when the taskbar button is created.
/// </summary>
/// <param name="e">The event args.</param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
wmTBC = RegisterWindowMessage("TaskbarButtonCreated");
}
/// <summary>
/// Handles the window message for notification of the taskbar button creation.
/// </summary>
/// <param name="m">The window message.</param>
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == wmTBC)
{
OnTaskbarButtonCreated();
}
}
/// <summary>
/// Override this method to recieve notification when the taskbar button is created on Windows 7 machines and above.
/// </summary>
protected void OnTaskbarButtonCreated()
{
if (TaskbarManager.IsPlatformSupported)
{
jumpList = JumpList.CreateJumpList();
jumpList.KnownCategoryToDisplay = JumpListKnownCategoryType.Recent;
jumpList.Refresh();
}
}
JumpList jumpList;
private void openToolStripMenuItem1_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
Open(ofd.FileName);
}
}
}
private void Open(string file)
{
try
{
if (!string.IsNullOrEmpty(file) && File.Exists(file))
{
textBox1.Text = File.ReadAllText(file);
if (TaskbarManager.IsPlatformSupported)
{
jumpList.AddToRecent(file);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <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.textBox1 = new System.Windows.Forms.TextBox();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.openToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 27);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(796, 306);
this.textBox1.TabIndex = 0;
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.openToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(820, 24);
this.menuStrip1.TabIndex = 1;
this.menuStrip1.Text = "menuStrip1";
//
// openToolStripMenuItem
//
this.openToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.openToolStripMenuItem1});
this.openToolStripMenuItem.Name = "openToolStripMenuItem";
this.openToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
this.openToolStripMenuItem.Text = "File";
//
// openToolStripMenuItem1
//
this.openToolStripMenuItem1.Name = "openToolStripMenuItem1";
this.openToolStripMenuItem1.Size = new System.Drawing.Size(152, 22);
this.openToolStripMenuItem1.Text = "Open";
this.openToolStripMenuItem1.Click += new System.EventHandler(this.openToolStripMenuItem1_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(820, 345);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.menuStrip1);
this.MainMenuStrip = this.menuStrip1;
this.Name = "Form1";
this.Text = "Form1";
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem1;
}

updating UI thread using BackGroundWorker in C# Form application

I have a time consuming task that tests a couple of network connections. In my example below I have confined it to one connection.
Normally the connection returns quickly but it could happen that the connection cannot be made so that the socket times out. During this time I would like to display an "idler" gif in the Form, when the connection succeeds the app should change the Image in the Form to some
green check icon or in case of a failing connection a red icon "stopper" should be displayed.
Somehow I cannot get the idler gif become visible and animated. To simulate a failing connection one can enter an invalid port # or non existent address.
Any clues what I'm missing or doing wrong?
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
///
#region BackgroundWorker
private System.ComponentModel.BackgroundWorker backgroundWorker = new System.ComponentModel.BackgroundWorker();
private delegate void SomeLongRunningMethodHandler(object sender, EventArgs e);
#endregion
private System.ComponentModel.IContainer components = null;
Button button,button2;
static Socket socket;
static bool success;
private static bool done;
private Label lbl1;
private Label lbl2;
private TextBox address;
private TextBox port;
private PictureBox p;
private static String port_number,host;
/// <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);
}
private void RunTest(object o,EventArgs e)
{
p.Visible = true;
SomeLongRunningMethodHandler synchronousFunctionHandler =
default(SomeLongRunningMethodHandler);
synchronousFunctionHandler =
TestConnection;
synchronousFunctionHandler.Invoke(o, e);
}
private void TestConnection(object o, EventArgs e)
{
host = address.Text;
port_number = port.Text;
if (null != socket)
{
socket.Close();
}
Thread.Sleep(1000);
IPEndPoint myEndpoint = new IPEndPoint(0, 0);
IPHostEntry remoteMachineInfo = Dns.GetHostEntry(host);
IPEndPoint serverEndpoint = new IPEndPoint(remoteMachineInfo.AddressList[0],
int.Parse(port_number));
socket = new Socket(myEndpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Connect(serverEndpoint);
success = true;
p.Image = global::BlockingUI.Properties.Resources.accept;
}
catch
{
success = false;
p.Image = global::BlockingUI.Properties.Resources.stopper;
}
done = true;
}
private void ExitApp(object o, EventArgs e)
{
Application.Exit();
}
}
If you truly want to use a BackgroundWorker, this (or something close to it), should point you in the right direction. You are creating a BackgroundWorker object but then doing nothing with it. BackgroundWorker objects are not allowed to access UI elements as they are owned by the UI thread, but you can pass in UI values, as I do here with a Tuple (you could create your own class to hold these values if you want too) and then modify the UI from the UI thread once the worker is complete.
private struct ConnectionProperties
{
public string Address;
public string Port;
}
private void RunTest(object o, EventArgs e)
{
BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
worker.RunWorkerCompleted += TestComplete;
worker.DoWork += TestConnection;
p.Visible = true;
//worker.RunWorkerAsync(new Tuple<string, string>(address.Text, port.Text));
worker.RunWorkerAsync(new ConnectionProperties{ Address = address.Text, Port = port.Text });
}
private void TestConnection(object sender, DoWorkEventArgs e)
{
bool success = false;
//var connection = e.Argument as Tuple<string, string>;
var connection = (ConnectionProperties)e.Argument;
if (null != socket)
{
socket.Close();
}
Thread.Sleep(1000);
IPEndPoint myEndpoint = new IPEndPoint(0, 0);
IPHostEntry remoteMachineInfo = Dns.GetHostEntry(connection.Address);
IPEndPoint serverEndpoint = new IPEndPoint(remoteMachineInfo.AddressList[0],
int.Parse(connection.Port));
socket = new Socket(myEndpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Connect(serverEndpoint);
success = true;
}
catch
{
success = false;
}
e.Result = success;
}
// Define other methods and classes here
private void TestComplete(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
var success = (bool)e.Result;
if (success)
{
p.Image = global::BlockingUI.Properties.Resources.accept;
}
else
{
p.Image = global::BlockingUI.Properties.Resources.stopper;
}
}
else
{
//unexpected error, show message or whatever
}
}
Well, I had done this with my own thread. Normally, you run the thread with the long running task, and when needed, call Control.Invoke() with a delegate pointing to a function that will operate over the UI. It looks that you're changing the UI using the background worker thread, which is not allowed.
Also, in Winforms is necessary to call Control.Invalidate() to force a re-painting of the UI and show the new icons.

Update excel spreadsheet from another thread

I've created COM server using C# where my clients can receive real time updates.
Updates usually fired from different threads.
But I've noted that Excel crashes when callback methods updates spreadsheet.
Is there any way to call updates in UI thread?
P.S. I know about RTD feature of Excel. But it doesn't suit my needs because i need several parameters in one update.
what you are looking for is the Invoke / BeginInvoke Method of ISyncronizedInvoke
on your UI thread, take an arbitrary control and keep that reference ...
from the thread that wants to fire an update, call Invoke or BeginInvoke on that control (Control implements ISyncronizedInvoke) with a delegate that you want to execute on the UI thread ... from that delegate you may call your COM server
//EDIT: example code
using System;
using System.Windows.Forms;
using System.Threading;
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Thread someWorkerThread;
Microsoft.Office.Interop.Excel.Application ExApp;
Worksheet wrkSheet;
public Form1()
{
InitializeComponent();
ExApp = new Microsoft.Office.Interop.Excel.Application();
ExApp.Visible = true; // or else we won't see the window
var books = ExApp.Workbooks;
var wrkBook = books.Add();
var sheets = wrkBook.Worksheets;
wrkSheet = sheets.get_Item(1);
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(wrkBook);
Marshal.ReleaseComObject(books);
someWorkerThread = new Thread(new ParameterizedThreadStart(threadHandler));
someWorkerThread.Start(this);
}
private void threadHandler(object obj)
{// this will be executed on a seperate worker thread
Control mainFrm = obj as Control;
if (mainFrm == null)
throw new ArgumentException("Need to have a Control as parameter");
for (int i = 1; i < 50;i++ )
{
Thread.Sleep(2500);
mainFrm.Invoke(new Action<int>(doStuff), i); // this will invoke the main UI thread
}
}
private void doStuff(int i)
{// this will be executed on the main UI thread
var range = wrkSheet.Range[string.Format("A{0}", i)];
range.Value = "Hello World!";
Marshal.ReleaseComObject(range);
}
#region designer stuff
/// <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.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(76, 84);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(149, 13);
this.label1.TabIndex = 0;
this.label1.Text = "I am an ordinary windows form";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 262);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
#endregion
}
}
be advised: this example does not clean up the wrkSheet reference and the ExApp reference ... you have to release them before you exit your application

Is this code [theoretically] thread-unsafe?

I'm experiencing a strange deadlock in the code that I've written.
The idea is to implement an asynchronous operation whose Stop is synchronous -- the caller has to wait until it completes. I've simplified the part where real work is done to a simple property increment (++Value, see below); in reality though, a heavy COM component is invoked which is very sensitive to threads.
The deadlock I'm experiencing is in the Stop() method where I explicitly wait for a manual-reset event that identifies a completed operation.
Any ideas what I could have done wrong? The code should be self-contained and compilable on its own.
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
using ThreadingTimer = System.Threading.Timer;
namespace CS_ManualResetEvent
{
class AsyncOperation
{
ThreadingTimer myTimer; //!< Receives periodic ticks on a ThreadPool thread and dispatches background worker.
ManualResetEvent myBgWorkerShouldIterate; //!< Fired when background worker must run a subsequent iteration of its processing loop.
ManualResetEvent myBgWorkerCompleted; //!< Fired before the background worker routine exits.
BackgroundWorker myBg; //!< Executes a background tasks
int myIsRunning; //!< Nonzero if operation is active; otherwise, zero.
public AsyncOperation()
{
var aTimerCallbac = new TimerCallback(Handler_Timer_Tick);
myTimer = new ThreadingTimer(aTimerCallbac, null, Timeout.Infinite, 100);
myBg = new BackgroundWorker();
myBg.DoWork += new DoWorkEventHandler(Handler_BgWorker_DoWork);
myBgWorkerShouldIterate = new ManualResetEvent(false);
myBgWorkerCompleted = new ManualResetEvent(false);
}
public int Value { get; set; }
/// <summary>Begins an asynchronous operation.</summary>
public void Start()
{
Interlocked.Exchange(ref myIsRunning, 1);
myTimer.Change(0, 100);
myBg.RunWorkerAsync(null);
}
/// <summary>Stops the worker thread and waits until it finishes.</summary>
public void Stop()
{
Interlocked.Exchange(ref myIsRunning, 0);
myTimer.Change(-1, Timeout.Infinite);
// fire the event once more so that the background worker can finish
myBgWorkerShouldIterate.Set();
// Wait until the operation completes; DEADLOCK occurs HERE!!!
myBgWorkerCompleted.WaitOne();
// Restore the state of events so that we could possibly re-run an existing component.
myBgWorkerCompleted.Reset();
myBgWorkerShouldIterate.Reset();
}
void Handler_BgWorker_DoWork(object sender, EventArgs theArgs)
{
while (true)
{
myBgWorkerShouldIterate.WaitOne();
if (myIsRunning == 0)
{
//Thread.Sleep(5000); //What if it takes some noticeable time to finish?
myBgWorkerCompleted.Set();
break;
}
// pretend we're doing some valuable work
++Value;
// The event will be set back in Handler_Timer_Tick or when the background worker should finish
myBgWorkerShouldIterate.Reset();
}
// exit
}
/// <summary>Processes tick events from a timer on a dedicated (thread pool) thread.</summary>
void Handler_Timer_Tick(object state)
{
// Let the asynchronous operation run its course.
myBgWorkerShouldIterate.Set();
}
}
public partial class Form1 : Form
{
private AsyncOperation myRec;
private Button btnStart;
private Button btnStop;
public Form1()
{
InitializeComponent();
}
private void Handler_StartButton_Click(object sender, EventArgs e)
{
myRec = new AsyncOperation();
myRec.Start();
btnStart.Enabled = false;
btnStop.Enabled = true;
}
private void Handler_StopButton_Click(object sender, EventArgs e)
{
myRec.Stop();
// Display the result of the asynchronous operation.
MessageBox.Show (myRec.Value.ToString() );
btnStart.Enabled = true;
btnStop.Enabled = false;
}
private void InitializeComponent()
{
btnStart = new Button();
btnStop = new Button();
SuspendLayout();
// btnStart
btnStart.Location = new System.Drawing.Point(35, 16);
btnStart.Size = new System.Drawing.Size(97, 63);
btnStart.Text = "Start";
btnStart.Click += new System.EventHandler(Handler_StartButton_Click);
// btnStop
btnStop.Enabled = false;
btnStop.Location = new System.Drawing.Point(138, 16);
btnStop.Size = new System.Drawing.Size(103, 63);
btnStop.Text = "Stop";
btnStop.Click += new System.EventHandler(Handler_StopButton_Click);
// Form1
ClientSize = new System.Drawing.Size(284, 94);
Controls.Add(this.btnStop);
Controls.Add(this.btnStart);
Text = "Form1";
ResumeLayout(false);
}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
It seems like all you're trying to do is have an asynchronous task that starts with the press of a button and stops when another button is pressed. Given that, you seem to be over-complicating the task. Consider using something designed for cancelling an asynchronous operation, such as a CancellationToken. The async task simply needs to check the cancellation token's status in the while loop (as opposed to while(true)) and the stop method becomes as simple as calling cancel on the CancellationTokenSource.
private CancellationTokenSource cancellationSource;
private Task asyncOperationCompleted;
private void button1_Click(object sender, EventArgs e)
{
//cancel previously running operation before starting a new one
if (cancellationSource != null)
{
cancellationSource.Cancel();
}
else //take out else if you want to restart here when `start` is pressed twice.
{
cancellationSource = new CancellationTokenSource();
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
asyncOperationCompleted = tcs.Task;
BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += (_, args) => DoWork(bgw, cancellationSource);
bgw.ProgressChanged += (_, args) => label1.Text = args.ProgressPercentage.ToString();
bgw.WorkerReportsProgress = true;
bgw.RunWorkerCompleted += (_, args) => tcs.SetResult(true);
bgw.RunWorkerAsync();
}
}
private void DoWork(BackgroundWorker bgw, CancellationTokenSource cancellationSource)
{
int i = 0;
while (!cancellationSource.IsCancellationRequested)
{
Thread.Sleep(1000);//placeholder for real work
bgw.ReportProgress(i++);
}
}
private void StopAndWaitOnBackgroundTask()
{
if (cancellationSource != null)
{
cancellationSource.Cancel();
cancellationSource = null;
asyncOperationCompleted.Wait();
}
}
put this code under ++Value; in Handler_BgWorker_DoWork. Then press the button when you see the output in debug window. Deadlock occurs then.
int i = 0;
while (i++ < 100) {
System.Diagnostics.Debug.Print("Press the button now");
Thread.Sleep(300);
Application.DoEvents();
}

Categories

Resources