I have wpf app. I have 3 labels, 1 button , (Start/Stop). I have databinding. My MainWindowViewModel loooks like beneath:
namespace StartStop
{
public class MainWindowViewModel : BindableBase
{
private Action<Action> RunOnUI;
CancellationTokenSource source;
public DelegateCommand StartCommand { get; set; }
private string startButtonText = "START";
public string StartButtonText
{
get { return startButtonText; }
set { SetProperty(ref startButtonText, value); }
}
private string loop1Value ;
public string Loop1Value
{
get { return loop1Value; }
set { SetProperty(ref loop1Value, value); }
}
private string loop2Value;
public string Loop2Value
{
get { return loop2Value; }
set { SetProperty(ref loop2Value, value); }
}
private string loop3Value;
public string Loop3Value
{
get { return loop3Value; }
set { SetProperty(ref loop3Value, value); }
}
public MainWindowViewModel(Action<Action> runOnUI)
{
RunOnUI = runOnUI;
StartCommand = new DelegateCommand(MainHandler);
}
async void MainHandler()
{
if (StartButtonText == "START")
{
StartButtonText = "STOP";
try
{
source = new CancellationTokenSource();
await Task.Run((Action)Loop1);
}
catch (OperationCanceledException)
{
MessageBox.Show("Test was aborted by user");
}
finally
{
StartButtonText = "START";
source = null;
}
}
else
{
source = null;
StartButtonText = "START";
}
}
private void Loop1()
{
for(int i=0;i<100;i++)
{
Loop1Value = "Loop1: " + i.ToString();
Sleep(1000, source.Token);
Loop2();
}
}
private void Loop2()
{
for (int i = 0; i < 100; i++)
{
Loop2Value = "Loop2: " + i.ToString();
Sleep(1000, source.Token);
Loop3();
}
}
private void Loop3()
{
for (int i = 0; i < 100; i++)
{
Loop3Value = "Loop3: " + i.ToString();
Sleep(1000, source.Token);
}
}
private static void Sleep(int millisecondsTimeout, CancellationToken cancellationToken)
{
Task.WaitAny(Task.Delay(millisecondsTimeout, cancellationToken));
}
}
}
Normally I have more advanced code and in Loops i have serial communication (rs232), I simplified code to show my problem.
When I press Stop button I want the thread stop (exit all loops). I tried to throw exception when Stop is pressed, buy setting source=null, but code doesn't go to catch. Why?
Change your loop method to
private void Loop1(CancellationToken token)
{
for(int i=0;i<100;i++)
{
token.ThrowIfCancellationIsRequested();
Loop1Value = "Loop1: " + i.ToString();
Sleep(1000, token.Token);
Loop2(token);
}
}
Do the same with all other loops. Also change your code for stopping to source.Cancel()
I would assume the Thread.Sleep is a stand in for reading a serial-port. Unfortunately SerialPort.Read does not seem to provide a overload with cancellation support. I'm not really faimilliar with serial ports, so yo might want to read Async Serial Port with CancellationToken and ReadTimeout. Or search for better libraries. Or if nothing else works, set a low timeout and catch the timeout exceptions.
I would also recommend to avoid mutable shared state when dealing with multiple threads. In this case it would mean to take the cancellation token and any other inputs as method parameters, and return some result. Avoid using any mutable fields. Updating the Loop1Value from a background thread looks like a very likely bug to me.
If you do not care if the operation was canceled or completed you could instead check the IsCancellationRequested property and simply return.
I want the thread stop
You need to use cooperative cancellation. Trying to stop a thread non-cooperativly is just not a good idea. See What's wrong with using Thread.Abort() for details.
Related
I want to send a progress bar that is available on my form to a function to display the progress of the operation of that function and finally return true if everything is fine and return false if else
The operation inside the SaveBadCustomerMustStayMasdoodExcelFile function is executed asynchronously so as not to interfere with the execution of other operations.
When the program runs and the compiler calls the line _ProgressBar.Invoke((MethodInvoker)(() => {_ProgressBar.Value = Convert.ToInt32(i);})); Inside the badCustomers.SendToDB(progressBar_BadCustomers) function, there is no feedback and the program seems to be stuck in an infinite loop.
But if the output of the SaveBadCustomerMustStayMasdoodExcelFile function is defined as void, everything works fine.
My code when the system hangs:
In Button:
private void btn_ChoiceBadCustomersFile_Click(object sender, EventArgs e)
{
try
{
DialogResult dialogResult = MessageBox.Show("message", "title",
MessageBoxButtons.YesNo);
if ((dialogResult == DialogResult.Yes))
{
bool result = false;
try
{
result = GetDataFromExcelFile.SaveBadCustomerMustStayMasdoodExcelFile(
progressBar_BadCustomers).Result;
}
catch
{
result = false;
}
if (result)
{
//code...
}
}
}
catch
{
//code...
}
}
Code In GetDataFromExcelFile.SaveBadCustomerMustStayMasdoodExcelFile(...)
public static class GetDataFromExcelFile
{
public async static Task<bool> SaveBadCustomerMustStayMasdoodExcelFile(
DevComponents.DotNetBar.Controls.ProgressBarX progressBar_BadCustomers)
{
try
{
PoomaDbAppEntities DB10 = new PoomaDbAppEntities();
IQueryable<tbl_BadCustomers> dt = null;
MyExcelWorkSpace _excelApp = new MyExcelWorkSpace();
MyExcelWorkSpace.badCustomers badCustomers = new MyExcelWorkSpace
.badCustomers();
string path = badCustomers.select();
if (path != String.Empty)
{
if (badCustomers.Open(path))
{
try
{
await Task.Run(() => { dt = badCustomers
.SendToDB(progressBar_BadCustomers); });
return true;
}
catch
{
return false;
}
}
else
{
return false;
}
}
else
{
return false;
}
}
catch
{
return false;
}
}
}
And In badCustomers.SendToDB(...) :
public class badCustomers : ExcelFile, IStartWorkWithExcelFile<tbl_BadCustomers>
{
//code
public IQueryable<tbl_BadCustomers> SendToDB(DevComponents.DotNetBar
.Controls.ProgressBarX _ProgressBar)
{
try
{
//code
_ProgressBar.Invoke((MethodInvoker)(() => {
_ProgressBar.Value = Convert.ToInt32(i); }));
}
catch
{
//code
}
}
IQueryable<tbl_BadCustomers> dt = DB10.tbl_BadCustomers.Select(i => i);
return dt;
}
}
If the SaveBadCustomerMustStayMasdoodExcelFile function is defined as below, the program works, but I need to know if an error has occurred or not.
public async static void SaveBadCustomerMustStayMasdoodExcelFile(
DevComponents.DotNetBar.Controls.ProgressBarX progressBar_BadCustomers)
{
//code
}
If the SaveBadCustomerMustStayMasdoodExcelFile function is defined as below, the program works, but I need to know if an error has occurred or not.
public async static void SaveBadCustomerMustStayMasdoodExcelFile(
DevComponents.DotNetBar.Controls.ProgressBarX progressBar_BadCustomers)
{
//code
}
there is no feedback and the program seems to be stuck in an infinite loop.
This is because the code is using Result; full details on my blog.
To solve this, you want to avoid async void (i.e., for SaveBadCustomerMustStayMasdoodExcelFile); async void is intended for event handlers, so you can make btn_ChoiceBadCustomersFile_Click an async void method. Everything else should use await and async Task.
As far as the Progress goes, I echo the recommendations in the comments: IProgress<T> with Progress<T> makes your code cleaner (e.g., no Invoke necessary).
We're trying to get status updates in the UI while the threads are running in the background. The following code is supposed to allow it but in practice we get the updates only once all threads are done and not while they are running. We also don't see significant performance improvement compared to running the task in serial so we might be doing something wrong here
The solution includes two projects with winForm with the first calling the second. WinClient namespace is used for the Winform client. It calls Services.modMain:
namespace WinClient
{
static class Program
{
[STAThread]
static void Main()
{
//call another winform project and wait for it to complete
Services.modMain.loadObjects().Wait();
//run local form
Application.Run(new Form1());
}
}
}
Service.modMain is where the application is continuously getting data and updating it in memory. When it does, it writes status messages to a splash form which remains open all the time. Once Service.modMain finishes the initial data load, Form1 (empty form in this exampl) should open while splashForm remains open as well
namespace Services
{
public static class modMain
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main()
{
}
public static async Task loadObjects()
{
frmSplash.DefInstance.LoadMe();
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
frmSplash.DefInstance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
frmSplash.DefInstance.PrintToSplashWindow(e);
}
}
}
PrintToSplashWindow is just a utility class to store progress data:
namespace Services
{
public class PrintToSplashMessage
{
public string Message { get; set; }
public Color MessageColor { get; set; }
public bool OnNewLine { get; set; }
public bool PrintToLog { get; set; }
public PrintToSplashMessage(String theMessage, Color theMessageColor, bool isOnNewLine, bool needPrintToLog)
{
Message = theMessage;
MessageColor = theMessageColor;
OnNewLine = isOnNewLine;
PrintToLog = needPrintToLog;
}
}
}
Finally, here's frmSplash:
namespace Services
{
public partial class frmSplash : Form
{
public frmSplash() :base()
{
InitializeComponent();
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if ( rtbErrorDisplay.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.SelectionColor = lngColor;
rtbErrorDisplay.SelectedText = strNewLine + strShortMsg;
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.ScrollToCaret();
Application.DoEvents();
}
}
}
What we expect is that frmSplash would show the progress messages as the tasks are runing in the background. In practice, it only show all at bulk when everything is done.
Short version: the only thing that ever processes window messages in the code you posted is a call to Application.DoEvents(). But the code likely never gets that far, or if it does, the call happens on the wrong thread.
Longer version:
You didn't include an actual MCVE, so I didn't bother to test, but the Progress class relies on a synchronization context to work. Since you haven't called Application.Run(), there's may be no sync context at all. In which case Progress is just going to use the thread pool to invoke whatever handlers subscribed to it.
That would mean that when you call Application.DoEvents(), you're in a thread pool thread, not the thread that owns your splash window.
Windows are owned by threads, and their messages go to that thread's message queue. The Application.DoEvents() method will retrieve messages for the current thread's message queue, but does nothing to process messages for other threads' queues.
In the worst case, there is a sync context for that thread (I can't recall…it's possible that since the thread is STA, the framework has created one for you), but since you have no message loop, nothing queued to it ever gets dispatched. The progress reports just keep piling up and never processed.
You should abandon Application.DoEvents() altogether. Calling DoEvents() is always a kludge, and there's always a better option.
In this case, use Application.Run() for the first form as well (the splash screen). Create that form and subscribe to its FormShown event so that you know when to call loadObjects(). At the end of that method, close the form, so Application.Run() will return and go on to the next Application.Run() call.
Here is a sample based on the code you did post, with me filling in the details (for both forms, just use the Designer to create a default Form object…the rest of the initialization is in the user code below).
For the splash screen class, I inferred most of it, and took the rest straight from your code. The only change I made to your code was to remove the call to Application.DoEvents():
partial class SplashScreen : Form
{
public static SplashScreen Instance { get; } = new SplashScreen();
private readonly RichTextBox richTextBox1 = new RichTextBox();
public SplashScreen()
{
InitializeComponent();
//
// richTextBox1
//
richTextBox1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
richTextBox1.Location = new Point(13, 13);
richTextBox1.Name = "richTextBox1";
richTextBox1.Size = new Size(775, 425);
richTextBox1.TabIndex = 0;
richTextBox1.Text = "";
Controls.Add(richTextBox1);
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if (richTextBox1.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.SelectionColor = lngColor;
richTextBox1.SelectedText = strNewLine + strShortMsg;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
It's not clear to me why you have two different classes, both of which seem to be set up as the entry point for the program. I consolidated those into a single class:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
loadObjects();
Application.Run(new Form1());
}
public static void loadObjects()
{
SplashScreen.Instance.Shown += async (sender, e) =>
{
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
SplashScreen.Instance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
SplashScreen.Instance.Close();
};
SplashScreen.Instance.ShowDialog();
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load2, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load1, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
SplashScreen.Instance.PrintToSplashWindow(e);
}
}
I have a search task that makes a request to an API, within a portable class library, when the user enters text in the textbox, this works as expected but I have a concern over performance at scale. When we have a large userbase all making requests to this API on every key press I can foresee performance issues.
I have limited the API call to only fire when there are more than three valid characters but I want to dampen this further. I could implement a timer over the top of this but it does not feel like a good solution and is not present in the PCL framework.
Is there a recommended pattern to achieve this type of request dampening?
private async Task GetClubs()
{
try
{
if (!string.IsNullOrWhiteSpace(ClubSearch) && ClubSearch.Replace(" ", "").Length >= 3)
{
Clubs = await dataService.GetClubs(ClubSearch);
}
}
catch (DataServiceException ex)
{
...
}
}
Usually that is done with timer. When search text changes you start (or reuse) a timer which will fire after delay and execute search request. If more text is typed during that delay - timer is reset. Sample code:
public class MyClass {
private readonly Timer _timer;
const int ThrottlePeriod = 500; // ms
public MyClass() {
_timer = new System.Threading.Timer(_ => {
ExecuteRequest();
}, null, Timeout.Infinite, Timeout.Infinite);
}
private string _searchTerm;
public string SearchTerm
{
get { return _searchTerm; }
set
{
_searchTerm = value;
ResetTimer();
}
}
private void ResetTimer() {
_timer.Change(ThrottlePeriod, Timeout.Infinite);
}
private void ExecuteRequest() {
Console.WriteLine(SearchTerm);
}
}
If timer is not available, you can do the same with Task.Delay:
public class MyClass
{
const int ThrottlePeriod = 500; // ms
private string _searchTerm;
public string SearchTerm
{
get { return _searchTerm; }
set
{
_searchTerm = value;
SearchWithDelay();
}
}
private async void SearchWithDelay() {
var before = this.SearchTerm;
await Task.Delay(ThrottlePeriod);
if (before == this.SearchTerm) {
// did not change while we were waiting
ExecuteRequest();
}
}
private void ExecuteRequest()
{
Console.WriteLine(SearchTerm);
}
}
Cheap/Fast way to implement this is a Task.Delay:
var mySearchThread = new Thread (new ThreadStart (async delegate {
while (true) {
if (!String.IsNullOrWhiteSpace(seachText) {
YourSearchMethod(seachText)
};
InvokeOnMainThread ( () => {
// Refresh your datasource on the UIthread
});
await Task.Delay (2000);
}
})).Start ();
A PCL-based solution (and amazing clean way with a great framework) is to use ReactiveUI throttling (Throttle), then you can do feats like:
// Throttle searching to every 2 seconds
this.WhenAnyValue(x => x.SearchText)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Throttle(TimeSpan.FromSeconds(2))
.InvokeCommand(SearchCommand)
Ref: http://reactiveui.net
Ref: http://docs.reactiveui.net/en/user-guide/when-any/index.html
I would like to run code alternatively, so I could stop execution at any moment. Is this code safe?
static class Program
{
static void Main()
{
var foo = new Foo();
//wait for interaction (this will be GUI app, so eg. btnNext_click)
foo.Continue();
//wait again etc.
foo.Continue();
foo.Continue();
foo.Continue();
foo.Continue();
foo.Continue();
}
}
class Foo
{
public Foo()
{
new Thread(Run).Start();
}
private void Run()
{
Break();
OnRun();
}
protected virtual void OnRun()
{
for (var i = 0; i < 5; i++)
{
Console.WriteLine(i);
Break();
}
//do something else and break;
}
private void Break()
{
lock (this)
{
Monitor.Pulse(this);
Monitor.Wait(this);
}
}
public void Continue()
{
lock (this)
{
Monitor.Pulse(this);
Monitor.Wait(this);
}
}
}
Of course I know, that now the application will never ends, but that's not the point.
I need this, because I would like to present steps in some kind of an algorithm and describe what is going on in particular moment, and making everything in one thread would lead to many complications even when using small amount of loops in the code. For example those lines:
for (var i = 0; i < 5; i++)
{
Console.WriteLine(i);
Break();
}
should be then replaced with:
if (this.i < 5)
{
Console.WriteLine(i++);
}
And that is just a small example of what I want to present. The code will be more complicated than a dummy for loop.
I recommend you check out this blog post about implementing fibers.
Code (In case the site goes down.)
public class Fiber
{
private readonly Stack<IEnumerator> stackFrame = new Stack<IEnumerator>();
private IEnumerator currentRoutine;
public Fiber(IEnumerator entryPoint)
{
this.currentRoutine = entryPoint;
}
public bool Step()
{
if (currentRoutine.MoveNext())
{
var subRoutine = currentRoutine.Current
as IEnumerator;
if (subRoutine != null)
{
stackFrame.Push(currentRoutine);
currentRoutine = subRoutine;
}
}
else if (stackFrame.Count > 0)
{
currentRoutine = stackFrame.Pop();
}
else
{
OnFiberTerminated(
new FiberTerminatedEventArgs(
currentRoutine.Current
)
);
return false;
}
return true;
}
public event EventHandler<FiberTerminatedEventArgs> FiberTerminated;
private void OnFiberTerminated(FiberTerminatedEventArgs e)
{
var handler = FiberTerminated;
if (handler != null)
{
handler(this, e);
}
}
}
public class FiberTerminatedEventArgs : EventArgs
{
private readonly object result;
public FiberTerminatedEventArgs(object result)
{
this.result = result;
}
public object Result
{
get { return this.result; }
}
}
class FiberTest
{
private static IEnumerator Recurse(int n)
{
Console.WriteLine(n);
yield return n;
if (n > 0)
{
yield return Recurse(n - 1);
}
}
static void Main(string[] args)
{
var fiber = new Fiber(Recurse(5));
while (fiber.Step()) ;
}
}
"...this will be GUI app..."
Then you probably do not want and will not have sequential code like above in Main().
I.e. the main GUI thread will not execute a serial code like above, but generally be idle, repainting, etc. or handling the Continue button click.
In that event handler you may better use an Auto|ManualResetEvent to signal the worker to proceed.
In the worker, just wait for the event.
I would suggest that any time one considers using Monitor.Wait(), one should write code so that it would work correctly if the Wait sometimes spontaneously acted as though it received a pulse. Typically, this means one should use the pattern:
lock(monitorObj)
{
while(notYetReady)
Monitor.Wait(monitorObj);
}
For your scenario, I'd suggest doing something like:
lock(monitorObj)
{
turn = [[identifier for this "thread"]];
Monitor.PulseAll(monitorObj);
while(turn != [[identifier for this "thread"]])
Monitor.Wait(monitorObj);
}
It is not possible for turn to change between its being checked whether it's the current thread's turn to proceed and the Monitor.Wait. Thus, if the Wait isn't skipped, the PulseAll is guaranteed to awaken it. Note that the code would work just fine if Wait spontaneously acted as though it received a pulse--it would simply spin around, observe turn wasn't set for the current thread, and go back to waiting.
I have this sample code for async operations (copied from the interwebs)
public class LongRunningTask
{
public LongRunningTask()
{
//do nowt
}
public int FetchInt()
{
Thread.Sleep(2000);
return 5;
}
}
public delegate TOutput SomeMethod<TOutput>();
public class GoodPerformance
{
public void BeginFetchInt()
{
LongRunningTask lr = new LongRunningTask();
SomeMethod<int> method = new SomeMethod<int>(lr.FetchInt);
// method is state object used to transfer result
//of long running operation
method.BeginInvoke(EndFetchInt, method);
}
public void EndFetchInt(IAsyncResult result)
{
SomeMethod<int> method = result.AsyncState as SomeMethod<int>;
Value = method.EndInvoke(result);
}
public int Value { get; set; }
}
Other async approaches I tried required the aysnc page attribute, they also seemed to cancel if other page elements where actioned on (a button clicked), this approach just seemed to work.
I’d like to add a cancel ability and exception handling for the longRunningTask class, but don’t erm, really know how.
In example:
public class ValueEventArgs : EventArgs
{
public int Value { get;set;}
}
public class ExceptionEventArgs : EventArgs
{
public Exception Exception { get;set;}
}
public class LongRunningTask
{
private bool canceled = false;
public event EventHandler<ValueEventArgs> Completed = delegate {}
public event EventHandler<ExceptionEventArgs> GotError = delegate {}
public void Cancel()
{
canceled = true;
}
public void FetchInt()
{
try
{
int result = 0;
for (int i = 0; i < 1000; i++)
{
if (canceled)
return;
result++;
}
Completed(this, new ValueEventArgs {Value = result});
}
catch(Exception exc)
{
GotError(this, new ExceptionEventArgs { Exception = exc });
}
}
public void BeginFetchInt()
{
ThreadPool.QueueUserWorkItem(i => FetchInt());
}
}
And somewhere:
LongRunningTask task = new LongRunningTask();
task.Completed +=new EventHandler<ValueEventArgs>(task_Completed);
task.GotError +=new EventHandler<ExceptionEventArgs>(task_GorError);
task.BeginFetchInt();
//in any moment until it calculates you may call:
task.Cancel();