Cancelling task in C# - c#

I have a Form with two buttons (Start , Stop).
When I press Start Button a Task is initialized and a function is called that keeps running until Stop button is pressed.
But When I press Stop button Form freezes. why?
I have copied snippet from StackOverflow but it still freezes Form.
So tell me how to Cancel Task properly?
public partial class Form1 : Form
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Task _task;
public Form1()
{
InitializeComponent();
}
//Funtion that runs when Task is initialized.
private void EventLoop(CancellationToken token)
{
//Code here..
while (!token.IsCancellationRequested)
{
//Code here..
}
if (token.IsCancellationRequested)
{
MessageBox.Show("Operation Complete..!!");
}
}
//Function for Start Button.
private void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
}
//Function for Stop button.
private void Stop_Click(object sender, EventArgs e)
{
_cts.Cancel();
}
}
Similar Example from MSDN:
var compute = Task.Factory.StartNew(() =>
{
return SumRootN(j);
}, tokenSource.Token);`
Form After Stop button is pressed.
token.IsCancellationRequested is true .
Full EventLoop() Function.
private void EventLoop(CancellationToken token)
{
SerialPort sp = new SerialPort();
string text, batch, part, courseName;
text = batch = part = courseName = "";
int courseId = 0;
this.Invoke((MethodInvoker)delegate()
{
text = portCB.SelectedItem.ToString();
batch = comboBox2.SelectedItem.ToString();
part = comboBox3.SelectedItem.ToString();
courseName = comboBox1.SelectedItem.ToString();
progressBar1.Value = 20;
using (Model1 db = new Model1())
{
courseId = db.Courses.Where(c => c.Course_name.ToUpper() == courseName.ToUpper()).Select(c => c.Course_Id).Single();
}
});
sp.PortName = text;
sp.BaudRate = 9600;
sp.Open();
while (!token.IsCancellationRequested)
{
text = sp.ReadLine();
if (text.Contains("Found ID #"))
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.Clear();
textBox2.Text = "Getting Registation ID.\n";
progressBar1.Value = 60;
});
string splitText = text.Split('#')[1];
int end = splitText.IndexOf(' ');
int id = int.Parse(splitText.Substring(0, end));
using (Model1 db = new Model1())
{
var result = db.Students.Where(s => s.Reg_Id == id && s.Batch == batch && s.Class == part).Select(s => s).SingleOrDefault();
if (result != null)
{
Attendance a = new Attendance();
a.Course_Id = courseId;
a.Student_Id = id;
a.Status = "P";
a.Date = DateTime.Today.Date;
a.Batch = batch;
a.Part = part;
db.Attendances.Add(a);
string message = "";
if (db.SaveChanges() != 0)
{
message = "Attendance Uploaded..!!\n";
}
else
{
message = "Attendance Not Uploaded ..!!\n";
}
this.Invoke((MethodInvoker)delegate()
{
progressBar1.Value = 100;
textBox2.AppendText(message);
});
}
else
{
this.BeginInvoke((MethodInvoker)delegate()
{
textBox2.AppendText("Student Does not belong to Specified Batch Or Part..\n");
});
}
}
}
else
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.AppendText("No Match Found..!! \n");
});
}
this.Invoke((MethodInvoker)delegate()
{
textBox1.AppendText(text + "\n");
});
}
sp.Close();
// This exception will be handled by the Task
// and will not cause the program to crash
if (token.IsCancellationRequested)
{
//MessageBox.Show("Operation Comptele..!!");
}
}

You call MessageBox.Show("Operation Complete..!!"); in the progress of cancellation. This is highly not recommended, not talking about that you are calling an UI operation from other than the UI thread.
Comment the MessageBox.Show("Operation Complete..!!"); line out
* Edit *
Question author's comment on his original question, found the mistake, which line was removed from the post. Here are my conclusion:
Always try to isolate an issue, and reproduce in its purest form. During that process you will probably diagnose and find the issues itself :-).
So if the code with issue is long to post, it is definitely not the way just deleting lines then post it. The way is deleting lines and see if the issue exist, with other words: Isolating the issue in its purest reproducable form

please use async call on your task
private async void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
await task;
}

Related

Using GetActiveTcpConnections to continuously check for new IP

I"m new to C# and am looking to see if the way I'm using this code to check for a new connection in a background worker is safe and or a better why to do it. My end goal is to run this as a service but, I'm using the background worker as a way to get real-time updates for testing. The code works but I don't know if its safe/stable or the best way to do it?
Any help would be appreciated.
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.RunWorkerAsync(2000);
MessageBox.Show("Started backgroung worker");
}
private void button2_Click(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.CancelAsync();
}
MessageBox.Show("Stopped backgroung worker");
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker helperBW = sender as BackgroundWorker;
e.Result = BackgroundProcessLogicMethod(helperBW, arg);
if (helperBW.CancellationPending)
{
e.Cancel = true;
}
}
private int BackgroundProcessLogicMethod(BackgroundWorker bw, int a)
{
while (true)
{
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
TcpConnectionInformation[] connections = properties.GetActiveTcpConnections();
foreach (TcpConnectionInformation c in connections)
{
if (c.RemoteEndPoint.ToString() == "192.168.4.14:443")
richTextBox1.Invoke(new Action( () => richTextBox1.AppendText(c.RemoteEndPoint.ToString() + "\n\r") ) );
}
}
}
Consider throttling the while(true)... loop but other than that you seem to be doing the right things in your post. This kind of thing shows up a lot in my own production code so I wanted to offer the tried-and-true approach that works for me. It's just another perspective, however.
Your code references RichTextBox which makes me infer that a WinForms example will fly here, but the general approach is valid regardless of platform anyway.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
checkBoxRun.CheckedChanged += onRunChanged;
}
CancellationTokenSource _cts = null;
private async void onRunChanged(object sender, EventArgs e)
{
if (checkBoxRun.Checked)
{
_cts = new CancellationTokenSource();
while (checkBoxRun.Checked)
{
richTextBox1.AppendTextOnUIThread(DateTime.Now.ToString());
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
TcpConnectionInformation[] connections = properties.GetActiveTcpConnections();
foreach (TcpConnectionInformation c in connections)
{
var endpoint = c.RemoteEndPoint.ToString();
switch (endpoint)
{
case "192.168.4.14:443":
richTextBox1.AppendTextOnUIThread(endpoint, color: Color.Red);
break;
case "127.0.0.1:49813":
richTextBox1.AppendTextOnUIThread(endpoint, color: Color.Blue);
break;
case "127.0.0.1:55846":
richTextBox1.AppendTextOnUIThread(endpoint, color: Color.Green);
break;
default:
continue;
}
}
richTextBox1.AppendTextOnUIThread();
// Throttle the polling to a reasonable repeat rate.
var task = Task.Delay(TimeSpan.FromSeconds(1), _cts.Token);
try{ await task; } catch (TaskCanceledException){ Debug.WriteLine("Cancelled"); }
}
}
else _cts.Cancel();
}
}
With the following extension for RichTextBox:
static class Extensions
{
public static void AppendTextOnUIThread(this RichTextBox #this, string text = "", bool newLine = true, Color? color = null)
{
if (!#this.Disposing)
{
if (newLine) text = $"{text}{Environment.NewLine}";
#this.BeginInvoke(new Action(() =>
{
if (color == null)
{
#this.AppendText(text);
}
else
{
var colorB4 = #this.ForeColor;
#this.SelectionColor = (Color)color;
#this.AppendText(text);
#this.SelectionColor = colorB4;
}
}));
}
}
}

Different behaviors when instantiated from button or timer in c#

I have a function called getMessages that can be called by a Button click (using the RelayCommand trigger) or that is called in a timer every 15s.
The desired behavior is:
webservice > deserialize answer > system notification > updatelistview > insert localDB
But when the function is called by the timer the updatelistview is not done. Why does this happen if the function is the same and works perfectly in the button command?
CODE:
// Get messages for the logged in user
public async void getMessages()
{
try
{
List<FriendGetMessage> msg = new List<FriendGetMessage>();
var response = await CommunicationWebServices.GetCHAT("users/" + au.idUser + "/get", au.token);
if (response.StatusCode == HttpStatusCode.OK) // If there are messages for me.
{
var aux = await response.Content.ReadAsStringAsync();
IEnumerable<FriendGetMessage> result = JsonConvert.DeserializeObject<IEnumerable<FriendGetMessage>>(aux);
if (result != null)
{
foreach (var m in result)
{
msg.Add(m);
}
//MsgList=msg;
foreach (var f in Friends)
{
if (f.msg == null || f.msg.Count() == 0)
{
f.msg = new ObservableCollection<Messages>();
}
foreach (var mess in msg)
{
if (mess.idUser == f.idUser)
{
Messages mm = new Messages();
mm.received = mess.message;
mm.timestamp = "Received " + mess.serverTimestamp;
mm.align = "Right";
// Add to the friend list.
f.msg.Add(mm);
// Add to Local DB
InsertMessage(null, au.idUser.ToString(), f.idUser, mess.message, mess.serverTimestamp);
var notification = new System.Windows.Forms.NotifyIcon()
{
Visible = true,
Icon = System.Drawing.SystemIcons.Information,
BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info,
BalloonTipTitle = "New Message from " + f.name,
BalloonTipText = "Message: " + mess.message,
};
// Display for 5 seconds.
notification.ShowBalloonTip(5);
// The notification should be disposed when you don't need it anymore,
// but doing so will immediately close the balloon if it's visible.
notification.Dispose();
}
}
}
counterChat = 1; // resets the counter
}
}
else {
counterChat = counterChat * 2;
}
//var sql = "select * from chat";
//var respo = GetFromDatabase(sql);
OnPropertyChanged("Friends");
}
catch (Exception e)
{
MessageBox.Show("GetMessages: " + e);
Debug.WriteLine("{0} Exception caught.", e);
}
}
CODE TIMER:
public void chatUpdate()
{
_timerChat = new DispatcherTimer(DispatcherPriority.Render);
_timerChat.Interval = TimeSpan.FromSeconds(15);
_timerChat.Tick += new EventHandler(timerchat_Tick);
_timerChat.Start();
}
public void timerchat_Tick(object sender, EventArgs e)
{
if (counterChat != incChat)
{
incChat++;
}
else
{
getMessages();
OnPropertyChanged("Friends");
incChat = 0;
}
}
ADDED - I've also tried this and didn't worked (it seems that is some kind of concurrency problem to the ObservableCollection called Friends (is a friendslist) each friend has an ObservableCollection of messages (is a chat))
public void chatUpdate()
{
_timerChat = new DispatcherTimer(DispatcherPriority.Render);
_timerChat.Interval = TimeSpan.FromSeconds(15);
_timerChat.Tick += new EventHandler(timerchat_Tick);
_timerChat.Start();
}
public async void timerchat_Tick(object sender, EventArgs e)
{
if (counterChat != incChat)
{
incChat++;
}
else
{
Application.Current.Dispatcher.Invoke((Action)async delegate { await getMessages(); });
incChat = 0;
}
}
Best regards,
I think you need to make the timer handler be an async method as follows:
public async void timerchat_Tick(object sender, EventArgs e)
{
if (counterChat != incChat)
{
incChat++;
}
else
{
await getMessages();
OnPropertyChanged("Friends");
incChat = 0;
}
}
This way OnPropertyChanged("Friends") is guaranteed to fire after the work in getMessages is done.
The methods need to change to:
DispatcherTimer _timerChat = new DispatcherTimer(DispatcherPriority.Render);
_timerChat.Interval = TimeSpan.FromSeconds(15);
_timerChat.Tick += new EventHandler(timerchat_Tick);
_timerChat.Start();
public async void timerchat_Tick(object sender, EventArgs e)
{
//...
await getMessages();
//...
}
public async Task getMessages()
{
try
{
// ... your code here
string result = await response.Content.ReadAsStringAsync();
// .... rest of your code
}
catch (Exception e)
{
MessageBox.Show("GetMessages: " + e);
}
}
It is solved. The problem was in my ViewModels I was opening multiple threads and sometimes the right one would update the UI and sometimes no.
Thanks for all the answers.

TargetInvokationException in Application.Run(new Form1());

I am trying to iterate through a for loop by pressing start button and stop it by pressing stop button. I am using await Task.Run(() => it works in the expected manner. But when I press start button again, I get TargetInvokationException in Application.Run(new Form1());.
My code below
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CancellationTest
{
public partial class Form1 : Form
{
private readonly SynchronizationContext synchronizationContext;
private DateTime previousTime = DateTime.Now;
CancellationTokenSource cts = new CancellationTokenSource();
public Form1()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
private async void ButtonClickHandlerAsync(object sender, EventArgs e)
{
button1.Enabled = false;
var count = 0;
CancellationToken token = cts.Token;
await Task.Run(() =>
{
try
{
for (var i = 0; i <= 5000000; i++)
{
token.ThrowIfCancellationRequested();
UpdateUI(i);
count = i;
}
}
catch (System.OperationCanceledException)
{
MessageBox.Show("Canceled");
}
}, token);
label1.Text = #"Counter " + count;
button1.Enabled = true;
}
public void UpdateUI(int value)
{
var timeNow = DateTime.Now;
if ((DateTime.Now - previousTime).Milliseconds <= 50) return;
synchronizationContext.Post(new SendOrPostCallback(o =>
{
label1.Text = #"Counter " + (int)o;
}), value);
previousTime = timeNow;
}
private void button2_Click(object sender, EventArgs e)
{
cts.Cancel();
}
}
}
Can anyone explain why this happens and how to resolve this.
Can anyone explain why this happens
TargetInvokationException is a wrapper type exception and the main information is contained in InnerException property which you didn't show. Looking at the code, most likely it is OperationCanceledException and is caused by passing an already canceled token to Task.Run.
In general you should not reuse the CancellationTokenSource instance - see The general pattern for implementing the cooperative cancellation model in the Remarks section of the documentation.
Also you should protect with try/catch the whole block, not only the Task.Run body. And initialize all the involved state members at the beginning, and do the necessary cleanup at the end.
So the correct code could be like this:
private DateTime previousTime;
private CancellationTokenSource cts;
private async void ButtonClickHandlerAsync(object sender, EventArgs e)
{
button1.Enabled = false;
var count = 0;
previousTime = DateTime.Now;
cts = new CancellationTokenSource();
try
{
CancellationToken token = cts.Token;
await Task.Run(() =>
{
for (var i = 0; i <= 5000000; i++)
{
token.ThrowIfCancellationRequested();
UpdateUI(i);
count = i;
}
}, token);
}
catch (System.OperationCanceledException)
{
MessageBox.Show("Canceled");
}
finally
{
cts.Dispose();
cts = null;
}
label1.Text = #"Counter " + count;
button1.Enabled = true;
}
and make sure Cancel button is enabled only when cts != null or check that condition inside the click handler in order to avoid NRE.

Increment ProgressBar within another class

it's my first question I'm asking here, so please be gentle with me ;)
So I've actually got two WinForms in my C# application I'm writing at the moment (I'm quite new to C#).
This window has a button, which saves photos from an usb device you selected before in a list box to another folder.
After clicking on this button my main thread is of course busy with copying, so I decided to create another WinForm which contains my ProgressBar.
Foreach completed copy, I want to increment my ProgressBar accordingly.
So I count the number of copies I have to do and give it the progressbar as maximum. But my problem at the moment is, that I really don't know how to increment the ProgressBar without getting a Thread Unsafe Exception.
Here's my ProgressWindow code:
public partial class ProgressWindow : Form
{
BackgroundWorker updateProgressBarThread = new BackgroundWorker();
private Boolean _isThreadRunning = false;
public Boolean IsThreadRunning
{
get { return _isThreadRunning; }
set { _isThreadRunning = value; }
}
private int _progressbarLength;
public int ProgressbarLength
{
get { return _progressbarLength; }
set { _progressbarLength = value; }
}
private int progress = 1;
public ProgressWindow()
{
Show();
InitializeComponent();
}
private void StartUpdateThread(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Reports progress to the ProgressChangedEvent function. (Thread Safe)
}
private void FinishProgressThread(object sender, RunWorkerCompletedEventArgs e)
{
if (!_isThreadRunning)
{
MessageBox.Show("Erfolgreich kopiert");
Close();
}
}
private void ProgressChangedEvent(object sender, ProgressChangedEventArgs e)
{
this.copyProgressbar.Value = e.ProgressPercentage;
this.progressStatus.Text = e.ProgressPercentage.ToString();
}
public void CallUpdateThread()
{
updateProgressBarThread.WorkerReportsProgress = true;
updateProgressBarThread.DoWork += new DoWorkEventHandler(StartUpdateThread);
updateProgressBarThread.ProgressChanged += new ProgressChangedEventHandler(ProgressChangedEvent);
updateProgressBarThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FinishProgressThread);
updateProgressBarThread.RunWorkerAsync();
}
}
I want to increment my ProgressBar with 1 after each succesful copy.
How do I do this from my main thread?
This is the function which actually handles the copy process
private void SaveFile(System.IO.DirectoryInfo root)
{
try
{
IEnumerable<DirectoryInfo> directoriesNames = root.EnumerateDirectories();
// New instance of thread ProgressWindow.
ProgressWindow progress = new ProgressWindow();
progress.CallUpdateThread();
foreach (DirectoryInfo element in directoriesNames)
{
// Query all subdirectories and count everything with the in the configuration made settings.
if (!element.Attributes.ToString().Contains("System"))
{
// Now we insert the configuration we applied.
String fileExtension = null;
if (Properties.Settings.Default._configPhoto)
{
fileExtension = "*.jpg";
}
if (Properties.Settings.Default._configWordDocument)
{
fileExtension = "*.odt";
}
FileInfo[] jpgList = element.GetFiles(fileExtension, SearchOption.AllDirectories);
// set the size of the progress bar
progress.ProgressbarLength = jpgList.Count();
// Now we go through all our results and save them to our backup folder.
foreach (FileInfo tmp in jpgList)
{
string sourceFilePath = tmp.FullName;
string destFilePath = PATHTOBACKUP + "\\" + tmp.Name;
progress.IsThreadRunning = true;
try
{
System.IO.File.Copy(sourceFilePath, destFilePath, true);
}
catch (IOException ioe)
{
MessageBox.Show(ioe.Message);
}
}
}
}
// progress.IsThreadRunning = false;
}
catch (UnauthorizedAccessException e)
{
MessageBox.Show(e.Message);
}
}
It's pretty obvious that I have to do this after this function
System.IO.File.Copy(sourceFilePath, destFilePath, true);
But how do I report this to my ProgressWindow?
I really hope I explained it well enough, not sure if I'm missing something important.
Thanks in advance guys
Here is a compact example of the key components:
Clicking button starts new thread worker
Progress is done by file lengths, not by number of files
BeginInvoke used to update the progress bar (avoid cross Thread exception)
ProgressBar pb = new ProgressBar() { Minimum = 0, Maximum = 100 };
Button btn = new Button();
btn.Click += delegate {
Thread t = new Thread(() => {
DirectoryInfo dir = new DirectoryInfo("C:\\temp\\");
var files = dir.GetFiles("*.txt");
long totalLength = files.Sum(f => f.Length);
long length = 0;
foreach (var f in files) {
length += f.Length;
int percent = (int) Math.Round(100.0 * length / totalLength);
pb.BeginInvoke((Action) delegate {
pb.Value = percent;
});
File.Copy(f.FullName, "...");
}
});
t.IsBackground = true;
t.Start();
};

Want to use Task Parallel Library with progress reporting for updating database

i developed a application where multiple connection string are stored. i just iterate in for loop and connect each db and execute sql against each db. in this way i update multiple database with bulk sql statement.
Now i need to use now Task Parallel Library to update multiple db simultaneously instead of updating one after one in loop. i also want to handle exception and also want to show multiple progress bar for multiple database operation. pause & resume functionality should be there.
when i will click on button then multiple db connection will be open and for each task a new progress bar will be added in my form. each progress bar will show each db operation progress. when any task will be finish then that corresponding progress bar will be removed from form.
any one can guide me with sample code how to do it using TPL.
here i got one code which update one progressbar but i need to update multiple progress bar.
int iterations = 100;
ProgressBar pb = new ProgressBar();
pb.Maximum = iterations;
pb.Dock = DockStyle.Fill;
Controls.Add(pb);
Task.Create(delegate
{
Parallel.For(0, iterations, i =>
{
Thread.SpinWait(50000000); // do work here
BeginInvoke((Action)delegate { pb.Value++; });
});
});
UPDATE Question
i did it in this way. code works but all progress bar value increase sequentially.
i have one form and one user control in winform apps. please have a look at my code and tell me what is wrong there.
main for code
public partial class Main : Form
{
public Main()
{
InitializeComponent();
this.DoubleBuffered = true;
}
private void btnStart_Click(object sender, EventArgs e)
{
Progress ucProgress = null;
Dictionary<string, string> dicList = new Dictionary<string, string>();
dicList.Add("GB", "conn1");
dicList.Add("US", "conn2");
dicList.Add("DE", "conn3");
fpPanel.Controls.Clear();
Task.Factory.StartNew(() =>
{
foreach (KeyValuePair<string, string> entry in dicList)
{
ucProgress = new Progress();
ucProgress.Country = entry.Key;
ucProgress.DBConnection = entry.Value;
fpPanel.BeginInvoke((MethodInvoker)delegate
{
fpPanel.Controls.Add(ucProgress);
ucProgress.Process();
});
//fpPanel.Controls.Add(ucProgress);
System.Threading.Thread.SpinWait(5000000);
}
});
}
private void Main_Resize(object sender, EventArgs e)
{
this.Invalidate();
}
private void Main_Paint(object sender, PaintEventArgs e)
{
using (LinearGradientBrush brush = new LinearGradientBrush(this.ClientRectangle,
Color.WhiteSmoke,
Color.LightGray,
90F))
{
e.Graphics.FillRectangle(brush, this.ClientRectangle);
}
}
}
user control code
public partial class Progress : UserControl
{
public Progress()
{
InitializeComponent();
lblMsg.Text = "";
pbStatus.Minimum = 0;
pbStatus.Maximum = 100;
}
public string Country { get; set; }
public string DBConnection { get; set; }
public string Sql { get; set; }
public void SetMessage(string strMsg)
{
lblMsg.Text = strMsg;
}
public void Process()
{
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
lblMsg.BeginInvoke((MethodInvoker)delegate
{
lblMsg.Text = "Connecting country " + Country;
});
pbStatus.BeginInvoke((MethodInvoker)delegate
{
pbStatus.Value = 30;
});
System.Threading.Thread.SpinWait(50000000);
//***********
lblMsg.BeginInvoke((MethodInvoker)delegate
{
lblMsg.Text = "executing sql for country " + Country;
});
pbStatus.BeginInvoke((MethodInvoker)delegate
{
pbStatus.Value = 60;
});
System.Threading.Thread.SpinWait(50000000);
//***********
lblMsg.BeginInvoke((MethodInvoker)delegate
{
lblMsg.Text = "sql executed successfully for country " + Country;
});
pbStatus.BeginInvoke((MethodInvoker)delegate
{
pbStatus.Value = 100;
});
System.Threading.Thread.SpinWait(50000000);
});
//System.Threading.Thread.SpinWait(50000000); // do work here
}
}
perhaps it can be starting point. Handling pause/resume depends from your needs and can be tweaked.
var cancellationTokenSource = new CancellationTokenSource();
var cancellation = cancellationTokenSource.Token;
void UpdateDatabases(IEnumerable<...> databases, CancellationToken cancellation)
{
foreach(db in databases)
{
//create as many ProgressBar instances as databases you want to update
//check if ProgressBar exist, then return it and reuse, otherwise create new
ProgressBar pb = new ProgressBar();
pb.Maximum = iterations;
pb.Dock = DockStyle.Fill;
Controls.Add(pb);
//start thread for every database/progress bar
Task.Factory.StartNew(progressBar =>
{
var start = (ProgressBar)progressBar).Value; //use last value in case of pause
Parallel.For(start, iterations,
new ParallelOptions(){CancellationToken = cancellation}
(i, loopState) =>
{
if (loopState.ShouldExitCurrentIteration)
return;
//perhaps check loopState.ShouldExitCurrentIteration inside worker method
Thread.SpinWait(50000000); // do work here
BeginInvoke((Action)delegate { ((ProgressBar)progressBar).Value++; });
});
},
pb, cancellation)
.ContinueWith(task =>
{
//to handle exceptions use task.Exception member
var progressBar = (ProgressBar)task.AsyncState;
if (!task.IsCancelled)
{
//hide progress bar here and reset pb.Value = 0
}
},
TaskScheduler.FromCurrentSynchronizationContext() //update UI from UI thread
);
}
}
//.........
//Call
UpdateDatabases(databases, cancellation)
//To suspend, call
cancellationTokenSource.Cancel();
//To resume - simply call UpdateDatabases again
cancellationTokenSource = new CancellationTokenSource();
cancellation = cancellationTokenSource.Token;
UpdateDatabases(databases, cancellation)
Update
I've reviewed your code. Take a look at the revisited code and adapt it to your needs. Main mistakes - mess with closures and creating the Progress from non-ui thread. To enable parallel processing you can use Parallel.ForEach (see MSND for possible overloads). Also the design looks little bit strange for me(you're mixing data and logic in the Progress ). From UI perspective it's also strange that progress bars will appear in order of processing but not in original order as they are in list (it will be a problem if you decide to sort the list alphabetically)
I hope it help.
main for code
private void btnStart_Click(object sender, EventArgs e)
{
Progress ucProgress = null;
Dictionary<string, string> dicList = new Dictionary<string, string>();
dicList.Add("GB", "conn1");
dicList.Add("US", "conn2");
dicList.Add("DE", "conn3");
fpPanel.Controls.Clear();
Func<KeyValuePair<string, string>, object> createProgress = entry =>
{
var tmp = new Progress {Country = entry.Key, DBConnection = entry.Value};
fpPanel.Controls.Add(tmp);
return tmp;
};
Task.Factory.StartNew(() =>
{
//foreach (KeyValuePair<string, string> entry in dicList)
Parallel.ForEach(dicList,
entry =>
{
//create and add the Progress in UI thread
var ucProgress = (Progress)fpPanel.Invoke(createProgress, entry);
//execute ucProgress.Process(); in non-UI thread in parallel.
//the .Process(); must update UI by using *Invoke
ucProgress.Process();
System.Threading.Thread.SpinWait(5000000);
});
});
}
user control code
public void Process()
{
//uiScheduler - Not used
//var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
//The Task is not necessary because the Process() called from Parallel.ForEach
//Task.Factory.StartNew(() =>
//{
//BeginInvoke or Invoke required
lblMsg.BeginInvoke((MethodInvoker)delegate
{
lblMsg.Text = "Connecting country " + Country;
});
pbStatus.BeginInvoke((MethodInvoker)delegate
{
pbStatus.Value = 30;
});
System.Threading.Thread.SpinWait(50000000);
//***********
lblMsg.BeginInvoke((MethodInvoker)delegate
{
lblMsg.Text = "executing sql for country " + Country;
});
pbStatus.BeginInvoke((MethodInvoker)delegate
{
pbStatus.Value = 60;
});
System.Threading.Thread.SpinWait(50000000);
//***********
lblMsg.BeginInvoke((MethodInvoker)delegate
{
lblMsg.Text = "sql executed successfully for country " + Country;
});
pbStatus.BeginInvoke((MethodInvoker)delegate
{
pbStatus.Value = 100;
});
System.Threading.Thread.SpinWait(50000000);
//});
//System.Threading.Thread.SpinWait(50000000); // do work here
}

Categories

Resources