Cancel thread and restart it - c#

When user resizes window some long text should be updated, but if the thread is already running it should be stopped and started over with new width parameter.
int myWidth;
private CancellationTokenSource tokenSource2 = new CancellationTokenSource();
private CancellationToken ct = new CancellationToken();
void container_Loaded(object sender, RoutedEventArgs e)
{
ct = tokenSource2.Token;
MyFunction();
}
void container_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (tokenSource2.Token.IsCancellationRequested)
MyFunction();
else
tokenSource2.Cancel();
}
void MyFunction()
{
myWidth = GetWidth();
Task.Factory.StartNew(() =>
{
string s;
for (int i=0;i<1000,i++){
s=s+Functionx(myWidth);
ct.ThrowIfCancellationRequested();
}
this.Dispatcher.BeginInvoke(new Action(() => {
ShowText(s);
}));
},tokenSource2.Token)
.ContinueWith(t => {
if (t.IsCanceled)
{
tokenSource2 = new CancellationTokenSource(); //reset token
MyFunction(); //restart
};
});
}
What now is happening is when I resize window I see text iteratively updating next several seconds as if old threads were not canceled. What am I doing wrong?

I don't think using global variables is a good idea in this case. Here's how I would do it by adding cancellation logic to my AsyncOp class from a related question. This code also implements the IProgress pattern and throttles the ViewModel updates.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Wpf_21611292
{
/// <summary>
/// Cancel and restarts an asynchronous operation
/// </summary>
public class AsyncOp<T>
{
readonly object _lock = new object();
Task<T> _pendingTask = null;
CancellationTokenSource _pendingCts = null;
public Task<T> CurrentTask
{
get { lock (_lock) return _pendingTask; }
}
public bool IsPending
{
get { lock (_lock) return _pendingTask != null && !_pendingTask.IsCompleted; }
}
public bool IsCancellationRequested
{
get { lock (_lock) return _pendingCts != null && _pendingCts.IsCancellationRequested; }
}
public void Cancel()
{
lock (_lock)
{
if (_pendingTask != null && !_pendingTask.IsCompleted && !_pendingCts.IsCancellationRequested)
_pendingCts.Cancel();
}
}
public Task<T> Run(
Func<CancellationToken, Task<T>> routine,
CancellationToken token = default,
bool startAsync = false,
bool continueAsync = false,
TaskScheduler taskScheduler = null)
{
Task<T> previousTask = null;
CancellationTokenSource previousCts = null;
Task<T> thisTask = null;
CancellationTokenSource thisCts = null;
async Task<T> routineWrapper()
{
// await the old task
if (previousTask != null)
{
if (!previousTask.IsCompleted && !previousCts.IsCancellationRequested)
{
previousCts.Cancel();
}
try
{
await previousTask;
}
catch (Exception ex)
{
if (!(previousTask.IsCanceled || ex is OperationCanceledException))
throw;
}
}
// run and await this task
return await routine(thisCts.Token);
};
Task<Task<T>> outerTask;
lock (_lock)
{
previousTask = _pendingTask;
previousCts = _pendingCts;
thisCts = CancellationTokenSource.CreateLinkedTokenSource(token);
outerTask = new Task<Task<T>>(
routineWrapper,
thisCts.Token,
continueAsync ?
TaskCreationOptions.RunContinuationsAsynchronously :
TaskCreationOptions.None);
thisTask = outerTask.Unwrap();
_pendingTask = thisTask;
_pendingCts = thisCts;
}
var scheduler = taskScheduler;
if (scheduler == null)
{
scheduler = SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() :
TaskScheduler.Default;
}
if (startAsync)
outerTask.Start(scheduler);
else
outerTask.RunSynchronously(scheduler);
return thisTask;
}
}
/// <summary>
/// ViewModel
/// </summary>
public class ViewModel : INotifyPropertyChanged
{
string _width;
string _text;
public string Width
{
get
{
return _width;
}
set
{
if (_width != value)
{
_width = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Width)));
}
}
}
public string Text
{
get
{
return _text;
}
set
{
if (_text != value)
{
_text = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// MainWindow
/// </summary>
public partial class MainWindow : Window
{
ViewModel _model = new ViewModel { Text = "Starting..." };
AsyncOp<DBNull> _asyncOp = new AsyncOp<DBNull>();
CancellationTokenSource _workCts = new CancellationTokenSource();
public MainWindow()
{
InitializeComponent();
this.DataContext = _model;
this.Loaded += MainWindow_Loaded;
this.SizeChanged += MainWindow_SizeChanged;
}
void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
_asyncOp.Run(WorkAsync, _workCts.Token);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_asyncOp.Run(WorkAsync, _workCts.Token);
}
async Task<DBNull> WorkAsync(CancellationToken token)
{
const int limit = 200000000;
var throttle = TimeSpan.FromMilliseconds(200);
// update ViewModel's Width
_model.Width = $"Width: {this.Width:#.##}";
// update ViewModel's Text using IProgress pattern
// and throttling updates
IProgress<int> progress = new Progress<int>(i =>
{
_model.Text = $"{(double)i / (limit - 1)* 100:0.}%";
});
var stopwatch = new Stopwatch();
stopwatch.Start();
// do some CPU-intensive work
await Task.Run(() =>
{
int i;
for (i = 0; i < limit; i++)
{
if (stopwatch.Elapsed > throttle)
{
progress.Report(i);
stopwatch.Restart();
}
if (token.IsCancellationRequested)
break;
}
progress.Report(i);
}, token);
return DBNull.Value;
}
}
}
XAML:
<Window x:Class="Wpf_21611292.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Width="200" Height="30" Text="{Binding Path=Width}"/>
<TextBox Width="200" Height="30" Text="{Binding Path=Text}"/>
</StackPanel>
</Window>
It uses async/await, so if you target .NET 4.0, you'd need Microsoft.Bcl.Async and VS2012+. Alternatively, you can convert async/await to ContinueWith, which is a bit tedious, but always possible (that's more or less what the C# 5.0 compiler does behind the scene).

You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Threading (for WPF) and add using System.Reactive.Linq; - then you can do this:
public MainWindow()
{
InitializeComponent();
_subscription =
Observable
.FromEventPattern<SizeChangedEventHandler, SizeChangedEventArgs>(
h => container.SizeChanged += h,
h => container.SizeChanged -= h)
.Select(e => GetWidth())
.Select(w => Observable.Start(
() => String.Concat(Enumerable.Range(0, 1000).Select(n => Functionx(w)))))
.Switch()
.ObserveOnDispatcher()
.Subscribe(t => ShowText(t));
}
private IDisposable _subscription = null;
That's all the code needed.
This responds to the SizeChanged event, calls GetWidth and then pushes the Functionx to another thread. It uses Switch() to always switch to the latest SizeChanged and then ignores any in-flight code. It pushes the result to the dispatcher and then calls ShowText.
If you need to close the form or stop the subscription running just call _subscription.Dispose().
Simple.

Related

Updating Winforms Label with Timer and Thread, stock app

Gist of it has probably been asked before, but I'm completely lost so I'm looking for some personal guidance. Been trying to make a stock tracker app for funsies using WinForms and the Yahoo API. Trying to get it so you can input a tracker symbol and it will make a new Label that will keep updating itself every so often. However, it keeps giving me error messages about "Cross-thread operation not valid". I've tried to do some googling, but yeah, completely lost. Here is most of the code, hope you guys can make some sense of it.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
{
public partial class Form1 : Form
{
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
}
}
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
public async void do_Things(string result)
{
string price;
Label label = null;
if (label == null)
{
price = await getStockPrices(result);
label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.Controls.Add(label);
}
else
{
Thread testThread = new Thread(async delegate ()
{
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
});
}
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
}
private void uiLockingTask() {
Thread.Sleep(5000);
}
}
}
Let me point out several things in your implementation.
You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
public partial class Form1 : Form
{
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}
private void onCancelClick()
{
_cancellationToken.Cancel();
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}
public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}
A much easier way is using async these days.
Here is a class which triggers an Action every interval:
public class UITimer : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
}
}
catch (OperationCanceledException) { }
}
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
}
If you work with dotnet 3.0 or higher, you can use the IAsyncDisposable. With this you're able to await the DisposeAsync method, so you can await the _timerTask to be finished.
And I created a new form with this as code behind:
public partial class Form1 : Form
{
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
{
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
}
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
{
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
}
}
The advantage is, that the callback runs on the UI thread but doesn't block it. The await Task.Delay(..) is using a Timer in the background, but posts the rest of the method/statemachine on the UI thread (because the UI thread has a SynchronizaionContext)
Easy but does the trick ;-)

C# Await Task for IEnumerator [duplicate]

In my C#/XAML metro app, there's a button which kicks off a long-running process. So, as recommended, I'm using async/await to make sure the UI thread doesn't get blocked:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Occasionally, the stuff happening within GetResults would require additional user input before it can continue. For simplicity, let's say the user just has to click a "continue" button.
My question is: how can I suspend the execution of GetResults in such a way that it awaits an event such as the click of another button?
Here's an ugly way to achieve what I'm looking for: the event handler for the continue" button sets a flag...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... and GetResults periodically polls it:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
The polling is clearly terrible (busy waiting / waste of cycles) and I'm looking for something event-based.
Any ideas?
Btw in this simplified example, one solution would be of course to split up GetResults() into two parts, invoke the first part from the start button and the second part from the continue button. In reality, the stuff happening in GetResults is more complex and different types of user input can be required at different points within the execution. So breaking up the logic into multiple methods would be non-trivial.
You can use an instance of the SemaphoreSlim Class as a signal:
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
// set signal in event
signal.Release();
// wait for signal somewhere else
await signal.WaitAsync();
Alternatively, you can use an instance of the TaskCompletionSource<T> Class to create a Task<T> that represents the result of the button click:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// complete task in event
tcs.SetResult(true);
// wait for task somewhere else
await tcs.Task;
When you have an unusual thing you need to await on, the easiest answer is often TaskCompletionSource (or some async-enabled primitive based on TaskCompletionSource).
In this case, your need is quite simple, so you can just use TaskCompletionSource directly:
private TaskCompletionSource<object> continueClicked;
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
// Note: You probably want to disable this button while "in progress" so the
// user can't click it twice.
await GetResults();
// And re-enable the button here, possibly in a finally block.
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
// Wait for the user to click Continue.
continueClicked = new TaskCompletionSource<object>();
buttonContinue.Visibility = Visibility.Visible;
await continueClicked.Task;
buttonContinue.Visibility = Visibility.Collapsed;
// More work...
}
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
if (continueClicked != null)
continueClicked.TrySetResult(null);
}
Logically, TaskCompletionSource is like an async ManualResetEvent, except that you can only "set" the event once and the event can have a "result" (in this case, we're not using it, so we just set the result to null).
Here is a utility class that I use:
public class AsyncEventListener
{
private readonly Func<bool> _predicate;
public AsyncEventListener() : this(() => true)
{
}
public AsyncEventListener(Func<bool> predicate)
{
_predicate = predicate;
Successfully = new Task(() => { });
}
public void Listen(object sender, EventArgs eventArgs)
{
if (!Successfully.IsCompleted && _predicate.Invoke())
{
Successfully.RunSynchronously();
}
}
public Task Successfully { get; }
}
And here is how I use it:
var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;
// ... make it change ...
await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;
Ideally, you don't. While you certainly can block the async thread, that's a waste of resources, and not ideal.
Consider the canonical example where the user goes to lunch while the button is waiting to be clicked.
If you have halted your asynchronous code while waiting for the input from the user, then it's just wasting resources while that thread is paused.
That said, it's better if in your asynchronous operation, you set the state that you need to maintain to the point where the button is enabled and you're "waiting" on a click. At that point, your GetResults method stops.
Then, when the button is clicked, based on the state that you have stored, you start another asynchronous task to continue the work.
Because the SynchronizationContext will be captured in the event handler that calls GetResults (the compiler will do this as a result of using the await keyword being used, and the fact that SynchronizationContext.Current should be non-null, given you are in a UI application), you can use async/await like so:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
// Show dialog/UI element. This code has been marshaled
// back to the UI thread because the SynchronizationContext
// was captured behind the scenes when
// await was called on the previous line.
...
// Check continue, if true, then continue with another async task.
if (_continue) await ContinueToGetResultsAsync();
}
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
ContinueToGetResultsAsync is the method that continues to get the results in the event that your button is pushed. If your button is not pushed, then your event handler does nothing.
Simple Helper Class:
public class EventAwaiter<TEventArgs>
{
private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();
private readonly Action<EventHandler<TEventArgs>> _unsubscribe;
public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
{
subscribe(Subscription);
_unsubscribe = unsubscribe;
}
public Task<TEventArgs> Task => _eventArrived.Task;
private EventHandler<TEventArgs> Subscription => (s, e) =>
{
_eventArrived.TrySetResult(e);
_unsubscribe(Subscription);
};
}
Usage:
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
Stephen Toub published this AsyncManualResetEvent class on his blog.
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
With Reactive Extensions (Rx.Net)
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
var res = await eventObservable.FirstAsync();
You can add Rx with Nuget Package System.Reactive
Tested Sample:
private static event EventHandler<EventArgs> _testEvent;
private static async Task Main()
{
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => _testEvent += h,
h => _testEvent -= h);
Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));
var res = await eventObservable.FirstAsync();
Console.WriteLine("Event got fired");
}
I'm using my own AsyncEvent class for awaitable events.
public delegate Task AsyncEventHandler<T>(object sender, T args) where T : EventArgs;
public class AsyncEvent : AsyncEvent<EventArgs>
{
public AsyncEvent() : base()
{
}
}
public class AsyncEvent<T> where T : EventArgs
{
private readonly HashSet<AsyncEventHandler<T>> _handlers;
public AsyncEvent()
{
_handlers = new HashSet<AsyncEventHandler<T>>();
}
public void Add(AsyncEventHandler<T> handler)
{
_handlers.Add(handler);
}
public void Remove(AsyncEventHandler<T> handler)
{
_handlers.Remove(handler);
}
public async Task InvokeAsync(object sender, T args)
{
foreach (var handler in _handlers)
{
await handler(sender, args);
}
}
public static AsyncEvent<T> operator+(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
var result = left ?? new AsyncEvent<T>();
result.Add(right);
return result;
}
public static AsyncEvent<T> operator-(AsyncEvent<T> left, AsyncEventHandler<T> right)
{
left.Remove(right);
return left;
}
}
To declare an event in the class that raises events:
public AsyncEvent MyNormalEvent;
public AsyncEvent<ProgressEventArgs> MyCustomEvent;
To raise the events:
if (MyNormalEvent != null) await MyNormalEvent.InvokeAsync(this, new EventArgs());
if (MyCustomEvent != null) await MyCustomEvent.InvokeAsync(this, new ProgressEventArgs());
To subscribe to the events:
MyControl.Click += async (sender, args) => {
// await...
}
MyControl.Click += (sender, args) => {
// synchronous code
return Task.CompletedTask;
}
Here is a small toolbox of six methods, that can be used for converting events to tasks:
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler)
{
var tcs = new TaskCompletionSource<object>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, EventArgs e)
{
removeHandler(Handler);
tcs.SetResult(null);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
addHandler(Handler);
return tcs.Task;
void Handler(object sender, TEventArgs e)
{
removeHandler(Handler);
tcs.SetResult(e);
}
}
/// <summary>Converts a .NET event, conforming to the standard .NET event pattern
/// based on a supplied event delegate type, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TDelegate, TEventArgs>(
Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
var tcs = new TaskCompletionSource<TEventArgs>();
TDelegate handler = default;
Action<object, TEventArgs> genericHandler = (sender, e) =>
{
removeHandler(handler);
tcs.SetResult(e);
};
handler = (TDelegate)(object)genericHandler.GetType().GetMethod("Invoke")
.CreateDelegate(typeof(TDelegate), genericHandler);
addHandler(handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler"/>, to a Task.</summary>
public static Task EventToAsync(object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<object>();
EventHandler handler = default;
handler = new EventHandler((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(null);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a named .NET event, conforming to the standard .NET event
/// pattern based on <see cref="EventHandler{TEventArgs}"/>, to a Task.</summary>
public static Task<TEventArgs> EventToAsync<TEventArgs>(
object target, string eventName)
{
var type = target.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo == null) throw new InvalidOperationException("Event not found.");
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = default;
handler = new EventHandler<TEventArgs>((sender, e) =>
{
eventInfo.RemoveEventHandler(target, handler);
tcs.SetResult(e);
});
eventInfo.AddEventHandler(target, handler);
return tcs.Task;
}
/// <summary>Converts a generic Action-based .NET event to a Task.</summary>
public static Task<TArgument> EventActionToAsync<TArgument>(
Action<Action<TArgument>> addHandler,
Action<Action<TArgument>> removeHandler)
{
var tcs = new TaskCompletionSource<TArgument>();
addHandler(Handler);
return tcs.Task;
void Handler(TArgument arg)
{
removeHandler(Handler);
tcs.SetResult(arg);
}
}
All these methods are creating a Task that will complete with the next invocation of the associated event. This task can never become faulted or canceled, it may only complete successfully.
Usage example with a standard event (Progress<T>.ProgressChanged):
var p = new Progress<int>();
//...
int result = await EventToAsync<int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<EventHandler<int>, int>(
h => p.ProgressChanged += h, h => p.ProgressChanged -= h);
// ...or...
int result = await EventToAsync<int>(p, "ProgressChanged");
Usage example with a non-standard event:
public static event Action<int> MyEvent;
//...
int result = await EventActionToAsync<int>(h => MyEvent += h, h => MyEvent -= h);
The event is unsubscribed when the task is completed. No mechanism is provided for unsubscribing earlier than that.
Here is a class I used for testing, which support CancellationToken.
This Test method shows us awaiting an instance of ClassWithEvent's MyEvent to be raised. :
public async Task TestEventAwaiter()
{
var cls = new ClassWithEvent();
Task<bool> isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(3));
cls.Raise();
Assert.IsTrue(await isRaisedTask);
isRaisedTask = EventAwaiter<ClassWithEvent>.RunAsync(
cls,
nameof(ClassWithEvent.MyMethodEvent),
TimeSpan.FromSeconds(1));
System.Threading.Thread.Sleep(2000);
Assert.IsFalse(await isRaisedTask);
}
Here's the event awaiter class.
public class EventAwaiter<TOwner>
{
private readonly TOwner_owner;
private readonly string _eventName;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
private readonly CancellationTokenSource _elapsedCancellationTokenSource;
private readonly CancellationTokenSource _linkedCancellationTokenSource;
private readonly CancellationToken _activeCancellationToken;
private Delegate _localHookDelegate;
private EventInfo _eventInfo;
public static Task<bool> RunAsync(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
return (new EventAwaiter<TOwner>(owner, eventName, timeout, cancellationToken)).RunAsync(timeout);
}
private EventAwaiter(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
if (owner == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(owner)));
if (eventName == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(eventName)));
_owner = owner;
_eventName = eventName;
_taskCompletionSource = new TaskCompletionSource<bool>();
_elapsedCancellationTokenSource = new CancellationTokenSource();
_linkedCancellationTokenSource =
cancellationToken == null
? null
: CancellationTokenSource.CreateLinkedTokenSource(_elapsedCancellationTokenSource.Token, cancellationToken.Value);
_activeCancellationToken = (_linkedCancellationTokenSource ?? _elapsedCancellationTokenSource).Token;
_eventInfo = typeof(TOwner).GetEvent(_eventName);
Type eventHandlerType = _eventInfo.EventHandlerType;
MethodInfo invokeMethodInfo = eventHandlerType.GetMethod("Invoke");
var parameterTypes = Enumerable.Repeat(this.GetType(),1).Concat(invokeMethodInfo.GetParameters().Select(p => p.ParameterType)).ToArray();
DynamicMethod eventRedirectorMethod = new DynamicMethod("EventRedirect", typeof(void), parameterTypes);
ILGenerator generator = eventRedirectorMethod.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, this.GetType().GetMethod(nameof(OnEventRaised),BindingFlags.Public | BindingFlags.Instance), null);
generator.Emit(OpCodes.Ret);
_localHookDelegate = eventRedirectorMethod.CreateDelegate(eventHandlerType,this);
}
private void AddHandler()
{
_eventInfo.AddEventHandler(_owner, _localHookDelegate);
}
private void RemoveHandler()
{
_eventInfo.RemoveEventHandler(_owner, _localHookDelegate);
}
private Task<bool> RunAsync(TimeSpan timeout)
{
AddHandler();
Task.Delay(timeout, _activeCancellationToken).
ContinueWith(TimeOutTaskCompleted);
return _taskCompletionSource.Task;
}
private void TimeOutTaskCompleted(Task tsk)
{
RemoveHandler();
if (_elapsedCancellationTokenSource.IsCancellationRequested) return;
if (_linkedCancellationTokenSource?.IsCancellationRequested == true)
SetResult(TaskResult.Cancelled);
else if (!_taskCompletionSource.Task.IsCompleted)
SetResult(TaskResult.Failed);
}
public void OnEventRaised()
{
RemoveHandler();
if (_taskCompletionSource.Task.IsCompleted)
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
}
else
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
SetResult(TaskResult.Success);
}
}
enum TaskResult { Failed, Success, Cancelled }
private void SetResult(TaskResult result)
{
if (result == TaskResult.Success)
_taskCompletionSource.SetResult(true);
else if (result == TaskResult.Failed)
_taskCompletionSource.SetResult(false);
else if (result == TaskResult.Cancelled)
_taskCompletionSource.SetCanceled();
Dispose();
}
public void Dispose()
{
RemoveHandler();
_elapsedCancellationTokenSource?.Dispose();
_linkedCancellationTokenSource?.Dispose();
}
}
It basically relies on CancellationTokenSource to report back the result.
It uses some IL injection to create a delegate to match the event's signature.
That delegate is then added as a handler for that event using some reflection.
The body of the generate method simply calls another function on the EventAwaiter class, which then reports success using the CancellationTokenSource.
Caution, do not use this, as is, in product. This is meant as a working example.
For instance, IL generation is an expensive process. You should avoid regenerate the same method over and over again, and instead cache these.
AsyncEx has AsyncManualResetEvent for this. You can:
var signal = new AsyncManualResetEvent();
await signal.WaitAsync();
And trigger it with:
signal.Set();

async/await not switching back to UI thread

I have timer which is calling list of stored actions. I want those actions to be called asynchronously. So I wrapped my CPU bound operation into task, then made async/await in action. However, it's not updating combo box. Clearly the context is not switching back to UI, but I don't understand why and what I should do to fix it.
Code in main form:
public FormMain()
{
InitializeComponent();
pt = new PeriodicTask(() => Execute());
pt.Start();
actions = new List<ActionWrapper>();
actions.Add(new ActionWrapper() { Periodic = false, MyAction = async () => {
bool b = await NetworkOperation();
comboBoxPairs.DataSource = pairs; // this doesn't update combo box
comboBoxPairs.DisplayMember = "Name";
//comboBoxPairs.Refresh(); // this is even crashing app
}});
}
private Task<bool> NetworkOperation()
{
return Task.Run(() => {
// CPU bound activity goes here
return true;
});
}
private void Execute()
{
Parallel.ForEach(actions,
new ParallelOptions { MaxDegreeOfParallelism = 10 },
x => {
x.MyAction();
if (!x.Periodic)
actions.Remove(x);
});
}
Timer class:
public class PeriodicTask
{
private System.Threading.Timer timer;
private int dueTime;
private int periodTime;
private Action callBack;
public PeriodicTask(Action cb)
{
callBack = cb;
timer = new System.Threading.Timer(Task, null, Timeout.Infinite, Timeout.Infinite);
dueTime = 100;
periodTime = 5000;
}
public void Start()
{
timer.Change(dueTime, periodTime);
}
public void Stop()
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
}
private void Task(object parameter)
{
callBack();
}
}
This is the wrapper class I use to hold action:
public class ActionWrapper
{
public bool Periodic { get; set; }
public Func<Task> MyAction { get; set; }
}
It's not that it does not switch back, it does not start on a UI thread in the first place because you are using System.Threading.Timer, which processes the ticks on thread pool threads, out of WPF context.
You can replace it with DispatcherTimer.
If a System.Timers.Timer is used in a WPF application, it is worth noting that the System.Timers.Timer runs on a different thread then the user interface (UI) thread. In order to access objects on the user interface (UI) thread, it is necessary to post the operation onto the Dispatcher of the user interface (UI) thread using Invoke or BeginInvoke. Reasons for using a DispatcherTimer opposed to a System.Timers.Timer are that the DispatcherTimer runs on the same thread as the Dispatcher and a DispatcherPriority can be set on the DispatcherTimer.
Also, you need to deal with re-entrance, same as for System.Threading.Timer, because a Cpu bound operation could still be processing the previous tick.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
DispatcherTimer timer = new DispatcherTimer();
long currentlyRunningTasksCount;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += async (s, e) =>
{
// Prevent re-entrance.
// Skip the current tick if a previous one is already in processing.
if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0)
{
return;
}
try
{
await ProcessTasks();
}
finally
{
Interlocked.Decrement(ref currentlyRunningTasksCount);
}
};
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// This one would crash, ItemsSource requires to be invoked from the UI thread.
// ThreadPool.QueueUserWorkItem(o => { listView.Items.Add("started"); });
listView.Items.Add("started");
timer.Start();
}
async Task ProcessTasks()
{
var computed = await Task.Run(() => CpuBoundComputation());
listView.Items.Add(string.Format("tick processed on {0} threads", computed.ToString()));
}
/// <summary>
/// Computes Cpu-bound tasks. From here and downstream, don't try to interact with the UI.
/// </summary>
/// <returns>Returns the degree of parallelism achieved.</returns>
int CpuBoundComputation()
{
long concurrentWorkers = 0;
return
Enumerable.Range(0, 1000)
.AsParallel()
.WithDegreeOfParallelism(Math.Max(1, Environment.ProcessorCount - 1))
.Select(i =>
{
var cur = Interlocked.Increment(ref concurrentWorkers);
SimulateExpensiveOne();
Interlocked.Decrement(ref concurrentWorkers);
return (int)cur;
})
.Max();
}
/// <summary>
/// Simulate expensive computation.
/// </summary>
void SimulateExpensiveOne()
{
// Prevent from optimizing out the unneeded result with GC.KeepAlive().
GC.KeepAlive(Enumerable.Range(0, 1000000).Select(i => (long)i).Sum());
}
}
}
If you need a precise control on what's happening, you are better off with queueing events and displaying them independently of processing:
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
DispatcherTimer fastTimer = new DispatcherTimer();
BackgroundProcessing processing = new BackgroundProcessing();
public MainWindow()
{
InitializeComponent();
processing.Start();
fastTimer.Interval = TimeSpan.FromMilliseconds(10);
fastTimer.Tick += Timer_Tick;
fastTimer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
Notification notification;
while ((notification = processing.TryDequeue()) != null)
{
listView.Items.Add(new { notification.What, notification.HappenedAt, notification.AttributedToATickOf });
}
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
processing.Stop();
}
}
public class Notification
{
public string What { get; private set; }
public DateTime AttributedToATickOf { get; private set; }
public DateTime HappenedAt { get; private set; }
public Notification(string what, DateTime happenedAt, DateTime attributedToATickOf)
{
What = what;
HappenedAt = happenedAt;
AttributedToATickOf = attributedToATickOf;
}
}
public class BackgroundProcessing
{
/// <summary>
/// Different kind of timer, <see cref="System.Threading.Timer"/>
/// </summary>
Timer preciseTimer;
ConcurrentQueue<Notification> notifications = new ConcurrentQueue<Notification>();
public Notification TryDequeue()
{
Notification token;
notifications.TryDequeue(out token);
return token;
}
public void Start()
{
preciseTimer = new Timer(o =>
{
var attributedToATickOf = DateTime.Now;
var r = new Random();
Parallel.ForEach(Enumerable.Range(0, 2), i => {
Thread.Sleep(r.Next(10, 5000));
var happenedAt = DateTime.Now;
notifications.Enqueue(
new Notification("Successfully loaded cpu 100%", happenedAt, attributedToATickOf));
});
}, null, 0, 1000);
}
public void Stop()
{
preciseTimer.Change(0, 0);
}
}
}
UPDATE:
For Windows Forms you could replace DispatcherTimer with the System.Windows.Forms.Timer in the second code sample.

Async Tasks Pause Resume of Loop [duplicate]

This question already has answers here:
A pattern to pause/resume an async task?
(5 answers)
Closed 5 years ago.
I have a sample code, taken from MSDN, to which I would like implement the Pause / Resume functionality within while loop. Could anybody propose a solution / parern which would hande this?
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
// Make a list of web addresses.
List<string> urlList = SetUpURLList();
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// ***Use ToList to execute the query and start the tasks.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
// Await the completed task.
int length = await firstFinishedTask;
resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
}
}
There is an MSDN article that solves this using a PauseToken (similar to a CancellationToken).
Here's the sample code from that article that demonstrates this concept:
namespace PauseTokenTestApp
{
public class PauseTokenSource
{
private volatile TaskCompletionSource<bool> m_paused;
internal static readonly Task s_completedTask = Task.FromResult(true);
public bool IsPaused
{
get { return m_paused != null; }
set
{
if (value)
{
Interlocked.CompareExchange(
ref m_paused, new TaskCompletionSource<bool>(), null);
}
else
{
while (true)
{
var tcs = m_paused;
if (tcs == null) return;
if (Interlocked.CompareExchange(ref m_paused, null, tcs) == tcs)
{
tcs.SetResult(true);
break;
}
}
}
}
}
public PauseToken Token { get { return new PauseToken(this); } }
internal Task WaitWhilePausedAsync()
{
var cur = m_paused;
return cur != null ? cur.Task : s_completedTask;
}
}
public struct PauseToken
{
private readonly PauseTokenSource m_source;
internal PauseToken(PauseTokenSource source) { m_source = source; }
public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }
public Task WaitWhilePausedAsync()
{
return IsPaused ?
m_source.WaitWhilePausedAsync() :
PauseTokenSource.s_completedTask;
}
}
class Program
{
static void Main()
{
var pts = new PauseTokenSource();
Task.Run(() =>
{
while (true)
{
Console.ReadLine();
pts.IsPaused = !pts.IsPaused;
}
});
SomeMethodAsync(pts.Token).Wait();
}
public static async Task SomeMethodAsync(PauseToken pause)
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i);
await Task.Delay(100);
await pause.WaitWhilePausedAsync();
}
}
}
}

CancellationTokenSource misbehaviour

I have a problem when i wait for a task after i have canceled it with the CancellationTokenSource. The cancel call does not interrupt the task. When i wait
for the task the main thread blocks because the task will be never interrupted.
Here is a short description my program:
A task increments a char variable (from 'A' to 'Z') and shows it on the GUI thread. In order to do this the task executes a delegate (this.invoke()) on the thread the control was created on.
As soon as i comment out the RefreshTextBox()-Function the cancel call works and the task will be interrupted. It seems as if the this.invoke() command prevents the task from interrupting.
I the code below i have also implemented the same functionality with normal threads. And then i works. Where is the difference between task implementation and thread implementation?
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
public partial class frm_Main : Form
{
private delegate void dgt_StringHandler(string str_Value);
CancellationTokenSource _obj_Cts = null;
Thread _obj_Thread = null;
Task _obj_Task = null;
public frm_Main()
{
InitializeComponent();
}
private void CreateChar(ref char chr_Value)
{
int int_Value;
int_Value = (int)chr_Value;
int_Value++;
if (int_Value > 90 || int_Value < 65)
int_Value = 65;
chr_Value = (char)int_Value;
}
private void TestThread()
{
char chr_Value = '#';
bool bol_Stop = false;
while (!bol_Stop)
{
try
{
Thread.Sleep(300);
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
catch (ThreadInterruptedException)
{
bol_Stop = true;
}
}
}
private void TestTask(object obj_TokenTmp)
{
char chr_Value = '#';
CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
while (!obj_Token.IsCancellationRequested)
{
Thread.Sleep(300);
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
}
private void RefreshTextBox(string str_Value)
{
if (txt_Value.InvokeRequired)
{
dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
this.Invoke(obj_StringHandler, new object[] { str_Value });
}
else
{
txt_Value.Text = str_Value;
}
}
private void btn_StartStop_Click(object sender, EventArgs e)
{
if (_obj_Task == null && _obj_Thread == null)
{
if (opt_Task.Checked)
{
_obj_Cts = new CancellationTokenSource();
_obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
_obj_Task.Start();
}
else
{
_obj_Thread = new Thread(new ThreadStart(TestThread));
_obj_Thread.Start();
}
btn_StartStop.Text = "Stop";
}
else
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
_obj_Thread = null;
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
_obj_Task = null;
_obj_Cts = null;
}
btn_StartStop.Text = "Start";
}
}
}
These 2 pieces of the code together form a deadlock:
_obj_Cts.Cancel();
_obj_Task.Wait();
and
this.Invoke(obj_StringHandler, new object[] { str_Value });
You are calling Wait() on the main thread, and Invoke() needs to be handled by the main thread.
You can break the deadlock by using this.BeginInvoke(...) instead.
The Thread version uses Interrupt, a sledgehammer. So the thread won't try to call RefreshTextBox() after the stop signal.
Here is the adapted code. I now call BeginInvoke() instead of Invoke() as Henk Holterman has proposed. This works very fine and is the only right way to prevent a deadlock. There is also another case that must be considered. I have also a IAsyncResult object that is given through the BeginInvoke() call. This object I use to check whether the async call has already completed or not. If I wouldn't check it it could be that the GUI thread is not fast enough (for example a sleep statement somewhere in the GUI thread) to execute my delegate and cause of this my TestTask() method would always call BeginInvoke() although the GUI thread has not already completed the last delegate. The result would be that my GUI thread would block the application.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
namespace InvokeTest
{
public partial class frm_Main : Form
{
private delegate void dgt_StringHandler(string str_Value);
CancellationTokenSource _obj_Cts = null;
Thread _obj_Thread = null;
Task _obj_Task = null;
IAsyncResult _obj_Ar = null;
public frm_Main()
{
InitializeComponent();
}
private void CreateChar(ref char chr_Value)
{
int int_Value;
int_Value = (int)chr_Value;
int_Value++;
if (int_Value > 90 || int_Value < 65)
int_Value = 65;
chr_Value = (char)int_Value;
}
private void TestThread()
{
char chr_Value = '#';
bool bol_Stop = false;
while (!bol_Stop)
{
try
{
Thread.Sleep(1); // is needed for interrupting the thread
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
catch (ThreadInterruptedException)
{
bol_Stop = true;
}
}
}
private void TestTask(object obj_TokenTmp)
{
char chr_Value = '#';
CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
while (!obj_Token.IsCancellationRequested)
{
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
}
private void RefreshTextBox(string str_Value)
{
if (txt_Value.InvokeRequired)
{
if (_obj_Ar == null ||
_obj_Ar.IsCompleted)
{
dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
_obj_Ar = this.BeginInvoke(obj_StringHandler, new object[] { str_Value });
}
}
else
{
Thread.Sleep(200);
txt_Value.Text = str_Value;
}
}
private void btn_StartStop_Click(object sender, EventArgs e)
{
if (_obj_Task == null && _obj_Thread == null)
{
if (opt_Task.Checked)
{
_obj_Cts = new CancellationTokenSource();
_obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
_obj_Task.Start();
}
else
{
_obj_Thread = new Thread(new ThreadStart(TestThread));
_obj_Thread.Start();
}
btn_StartStop.Text = "Stop";
}
else
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
_obj_Thread = null;
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
_obj_Task = null;
_obj_Cts = null;
}
btn_StartStop.Text = "Start";
}
}
private void frm_Main_FormClosing(object sender, FormClosingEventArgs e)
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
}
}
}
}

Categories