Hi,
I am doing a parallel task using thread pool. When user clicks go button, I simply disable the go button until all the tasks are completed. I am checking button.InvokeRequired before setting the enabled property to true or false.
I facing a problem here,
1. When I let all the tasks to complete themselves before enabling the go button again then I am able to fire a click event on the button.
2. But when I cancel the remaining tasks in between and re-enable the button again, its not responding to any click events.
I finally narrowed down to setting e.cancel on TabPage_validating from different thread which is doing something wrong here and is making complete tab page not responding to events. Cancel property seems to be in sharedstate and is not getting set propely... what's the correct solution. Please help.
Following is the complete source code of a POC where I've replicated the issue,
namespace POCUI
{
partial class ThreadingTabIssue
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.btnCancel = new System.Windows.Forms.Button();
this.lblError = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.btnStart = new System.Windows.Forms.Button();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.SuspendLayout();
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Location = new System.Drawing.Point(0, 0);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(869, 562);
this.tabControl1.TabIndex = 0;
this.tabPage1.Controls.Add(this.btnCancel);
this.tabPage1.Controls.Add(this.lblError);
this.tabPage1.Controls.Add(this.textBox1);
this.tabPage1.Controls.Add(this.btnStart);
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(861, 536);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "tabPage1";
this.tabPage1.UseVisualStyleBackColor = true;
this.tabPage1.Validating += new System.ComponentModel.CancelEventHandler(this.tabPage1_Validating);
this.btnCancel.Location = new System.Drawing.Point(147, 6);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(89, 23);
this.btnCancel.TabIndex = 3;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
this.lblError.AutoSize = true;
this.lblError.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblError.ForeColor = System.Drawing.Color.Red;
this.lblError.Location = new System.Drawing.Point(242, 11);
this.lblError.Name = "lblError";
this.lblError.Size = new System.Drawing.Size(0, 13);
this.lblError.TabIndex = 2;
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(3, 35);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox1.Size = new System.Drawing.Size(855, 498);
this.textBox1.TabIndex = 1;
this.btnStart.Location = new System.Drawing.Point(3, 6);
this.btnStart.Name = "btnStart";
this.btnStart.Size = new System.Drawing.Size(138, 23);
this.btnStart.TabIndex = 0;
this.btnStart.Text = "Start Long Operation";
this.btnStart.UseVisualStyleBackColor = true;
this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
this.tabPage2.Size = new System.Drawing.Size(861, 536);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "tabPage2";
this.tabPage2.UseVisualStyleBackColor = true;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(869, 562);
this.Controls.Add(this.tabControl1);
this.Name = "ThreadingTabIssue";
this.Text = "ThreadingTabIssue";
this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.tabPage1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button btnStart;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.Label lblError;
private System.Windows.Forms.Button btnCancel;
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using System.Threading;
using POCLib;
namespace POCUI
{
public partial class ThreadingTabIssue : Form
{
private Int32 _numberOfObjects;
AutoResetEvent _doneEvent;
List<SpreadSheetHandler> handlers;
protected Boolean SendOperationInProgress { get; set; }
protected Boolean IsCancelled { get; set; }
public ThreadingTabIssue()
{
InitializeComponent();
ThreadPool.SetMaxThreads(100, 100);
}
private void btnStart_Click(object sender, EventArgs e)
{
_numberOfObjects = 65;
_doneEvent = new AutoResetEvent(false);
this.ActivateDeactivateUI(false);
StartSpreadsheetEngine();
}
protected void StartSpreadsheetEngine()
{
try
{
Thread mainThread = new Thread(new ThreadStart(ProcesTasks));
mainThread.IsBackground = true;
mainThread.Name = "Main Worker Thread";
mainThread.Start();
}
catch (Exception ee)
{
this.SetControlProperty(lblError, "Text", ee.Message);
}
}
private void ProcesTasks()
{
try
{
IsCancelled = false;
this.SetControlProperty(lblError, "Text", "Please Wait");
this.SetControlProperty(textBox1, "Text", "Item Status\r\n \r\n ");
handlers = new List<SpreadSheetHandler>();
SendOperationInProgress = true;
for (Int32 i = 0; i < _numberOfObjects; i++)
{
handlers.Add(ParallelSpreadSheetHandler.QueueSpreadSheet(Task, i));
}
_doneEvent.WaitOne();
if (!IsCancelled)
this.SetControlProperty(lblError, "Text", "Done");
else
this.SetControlProperty(lblError, "Text", "Cancelled");
this.ActivateDeactivateUI(true);
SendOperationInProgress = false;
}
catch (Exception ee)
{
this.SetControlProperty(lblError, "Text", ee.Message);
}
}
protected void Task(Object obj)
{
try
{
Int32 inst = (Int32)obj;
ThreadingTabIssueImplementation implementation = new ThreadingTabIssueImplementation();
SpreadsheetItem item = implementation.DoSomething(inst);
this.UpdateText(item);
}
catch (Exception) { }
finally { if (Interlocked.Decrement(ref _numberOfObjects) == 0) { _doneEvent.Set(); } }
}
protected void CancelSend(SpreadSheetHandler[] items)
{
this.SetControlProperty(lblError, "Text", "Please wait while operation is cancelled");
foreach (SpreadSheetHandler item in items)
{
SpreadsheetStatus status = ParallelSpreadSheetHandler.Cancel(item, false);
if (status == SpreadsheetStatus.Cancelled)
{
if (Interlocked.Decrement(ref _numberOfObjects) == 0) { _doneEvent.Set(); }
}
}
}
#region UI Update Code
private void UpdateText(SpreadsheetItem item)
{
try
{
this.UpdateTextBoxStatus(item);
}
catch { }
}
delegate void updatetext(SpreadsheetItem item);
void UpdateTextBoxStatus(SpreadsheetItem item)
{
if (textBox1.InvokeRequired)
{
textBox1.BeginInvoke(new updatetext(UpdateTextBoxStatus),
new object[] { item });
}
else { textBox1.Text = textBox1.Text + "\r\n" + item.Message; }
}
delegate DialogResult ShowCustomMessage(String value);
DialogResult ShowDialog(String value)
{
if (InvokeRequired)
{
BeginInvoke(new ShowCustomMessage(ShowDialog), new object[] { value });
return DialogResult.OK;
}
return MessageBox.Show(value, "Test Poc", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
}
private delegate void ControlPropertyDel(Control control, string propertyName, object propertyValue);
void SetControlProperty(Control cntrl, string propertyName, object propertyValue)
{
try
{
if (InvokeRequired)
{
BeginInvoke(new ControlPropertyDel(SetControlProperty),
new object[] { cntrl, propertyName, propertyValue });
}
else
cntrl.GetType().InvokeMember(propertyName, BindingFlags.SetProperty,
null, cntrl, new object[] { propertyValue });
}
catch { }
}
void ActivateDeactivateUI(Boolean value)
{
try
{
this.SetControlProperty(btnStart, "Enabled", value);
}
catch (Exception ee) { throw ee; }
}
private delegate void ValidatingEvent(object sender, System.ComponentModel.CancelEventArgs e);
private void tabPage1_Validating(object sender, CancelEventArgs e)
{
if (tabPage1.InvokeRequired)
{
tabPage1.BeginInvoke(new ValidatingEvent(tabPage1_Validating),
new object[] { sender, e });
}
else
{
if (this.SendOperationInProgress)
{
e.Cancel = true;
if (this.ShowDialog("Do you want to cancel") == DialogResult.OK)
{
IsCancelled = true;
this.CancelSend(this.handlers.ToArray());
}
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (this.SendOperationInProgress)
{
if (this.ShowDialog("Do you want to cancel") == DialogResult.OK)
{
IsCancelled = true;
this.CancelSend(this.handlers.ToArray());
}
}
else { this.ShowDialog("Nothing to cancel"); }
}
#endregion UI Update Code
}
}
using System;
using System.Collections.Generic;
using System.Threading;
namespace POCLib
{
public sealed class SpreadSheetHandler
{
private WaitCallback appCallBack;
private Object objState;
private ExecutionContext appContext;
internal SpreadSheetHandler(WaitCallback wCallBack, Object oState, ExecutionContext context)
{
appCallBack = wCallBack;
objState = oState;
appContext = context;
}
internal WaitCallback AppCallback { get { return appCallBack; } }
internal object ObjectState { get { return objState; } }
internal ExecutionContext Context { get { return appContext; } }
}
public enum SpreadsheetStatus
{
Completed, Cancelled, Executing, Aborted
}
public static class ParallelSpreadSheetHandler
{
private static LinkedList<SpreadSheetHandler> appCallBacks =
new LinkedList<SpreadSheetHandler>();
private static Dictionary<SpreadSheetHandler, Thread> executingThreads =
new Dictionary<SpreadSheetHandler, Thread>();
public static SpreadSheetHandler QueueSpreadSheet(
WaitCallback callback, object state)
{
if (callback == null) throw new ArgumentNullException("Spreadsheet Callback method is null");
SpreadSheetHandler item = new SpreadSheetHandler(
callback, state, ExecutionContext.Capture());
lock (appCallBacks) appCallBacks.AddLast(item);
ThreadPool.QueueUserWorkItem(new WaitCallback(HandleItem));
return item;
}
private static void HandleItem(object ignored)
{
SpreadSheetHandler item = null;
try
{
lock (appCallBacks)
{
if (appCallBacks.Count > 0)
{
item = appCallBacks.First.Value;
appCallBacks.RemoveFirst();
}
if (item == null) return;
executingThreads.Add(item, Thread.CurrentThread);
}
ExecutionContext.Run(item.Context,
delegate { item.AppCallback(item.ObjectState); }, null);
}
finally
{
lock (appCallBacks)
{
if (item != null) executingThreads.Remove(item);
}
}
}
public static SpreadsheetStatus Cancel(SpreadSheetHandler item, bool allowAbort)
{
if (item == null) throw new ArgumentNullException("Parallet Sheet handler is null");
lock (appCallBacks)
{
LinkedListNode<SpreadSheetHandler> node = appCallBacks.Find(item);
if (node != null)
{
appCallBacks.Remove(node);
return SpreadsheetStatus.Cancelled;
}
else if (executingThreads.ContainsKey(item))
{
if (allowAbort)
{
executingThreads[item].Abort();
executingThreads.Remove(item);
return SpreadsheetStatus.Aborted;
}
else return SpreadsheetStatus.Executing;
}
else return SpreadsheetStatus.Completed;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Threading;
namespace POCLib
{
public class ThreadingTabIssueImplementation
{
public SpreadsheetItem DoSomething(Int32 instance)
{
SpreadsheetItem retVal = new SpreadsheetItem();
try
{
Thread.Sleep(4000);
retVal.Message = "Completed on : " + instance;
retVal.ItemStatus = SpreadsheetItemStatus.Success;
}
catch (Exception ee)
{
retVal.ItemStatus = SpreadsheetItemStatus.HardError;
retVal.Message = "Error on : " + instance.ToString() + ", Message : " + ee.Message;
}
return retVal;
}
}
[Serializable]
public class SpreadsheetItem
{
private String _message = String.Empty;
public Int32 Identity { get; set; }
public SpreadsheetItemStatus ItemStatus { get; set; }
public String Message { get { return _message; } set { _message = value; } }
public EventWaitHandle ResetEvent { get; set; }
}
[Serializable]
public enum SpreadsheetItemStatus
{
None, Warning, HardError, Success
}
}