How to use CancellationTokenSource to close a dialog on another thread? - c#

This is related to my other question How to cancel background printing.
I am trying to better understand the CancellationTokenSource model and how to use it across thread boundaries.
I have a main window (on the UI thread) where the code behind does:
public MainWindow()
{
InitializeComponent();
Loaded += (s, e) => {
DataContext = new MainWindowViewModel();
Closing += ((MainWindowViewModel)DataContext).MainWindow_Closing;
};
}
which correctly calls the CloseWindow code when it is closed:
private void CloseWindow(IClosable window)
{
if (window != null)
{
windowClosingCTS.Cancel();
window.Close();
}
}
With the selection of a menu item, a second window is created on a background thread:
// Print Preview
public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
// Was cancellation already requested?
if (ct.IsCancellationRequested)
ct.ThrowIfCancellationRequested();
...............................
// Use my custom document viewer (the print button is removed).
var previewWindow = new PrintPreview(fixedDocumentSequence);
//Register the cancellation procedure with the cancellation token
ct.Register(() =>
previewWindow.Close()
);
previewWindow.ShowDialog();
}
}
In the MainWindowViewModel (on the UI thread), I put:
public CancellationTokenSource windowClosingCTS { get; set; }
With its constructor of:
// Constructor
public MainMenu()
{
readers = new List<Reader>();
CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
windowClosingCTS = new CancellationTokenSource();
}
Now my problem. When closing the MainWindow on the UI thread, windowClosingCTS.Cancel() causes an immediate call to the delegate registered with ct, i.e. previewWindow.Close() is called. This now throws immediately back to the " If (Windows != null) with:
"The calling thread cannot access this object because a different
thread owns it."
So what am I doing wrong?

Your problem is that your preview window runs on another thread. When you trigger cancellation, you execute the registered action of the cancellation token on that thread, not on the thread your preview is running on.
The gold standard in these cases is to not use two UI threads. This will usually cause trouble and the work you need to handle them is usually not worth it.
If you want to stay with your solution or if you want to trigger cancellation from a background thread, you have to marshal your close operation to the thread your window is opened in:
Action closeAction = () => previewWindow.Close();
previewWindow.Dispatcher.Invoke(closeAction);

The problem with your code is
With the selection of a menu item, a second window is created on a
background thread:
// Print Preview
public static void PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
// Was cancellation already requested?
if (ct.IsCancellationRequested)
ct.ThrowIfCancellationRequested();
...............................
// Use my custom document viewer (the print button is removed).
var previewWindow = new PrintPreview(fixedDocumentSequence);
//Register the cancellation procedure with the cancellation token
ct.Register(() =>
previewWindow.Close()
);
previewWindow.ShowDialog();
}
}
And what I presume to be
Task.Run(() => PrintPreview(foo, cancel));
The correct solution is to do everything on a single thread.
public static Task<bool> PrintPreview(FixedDocument fixeddocument, CancellationToken ct)
{
var tcs = new TaskCompletionSource<bool>();
// Was cancellation already requested?
if (ct.IsCancellationRequested)
tcs.SetResult(false);
else
{
// Use my custom document viewer (the print button is removed).
var previewWindow = new PrintPreview(fixedDocumentSequence);
//Register the cancellation procedure with the cancellation token
ct.Register(() => previewWindow.Close());
previewWindow.Closed += (o, e) =>
{
var result = previewWindow.DialogResult;
if (result.HasValue)
tcs.SetResult(result.Value);
else
tcs.SetResult(false);
}
previewWindow.Show();
}
return tcs.Task;
}
Then call
var shouldPrint = await PrintPreview(foo, cancel);
if (shouldPrint)
await PrintAsync(foo);

Related

Cancelling an Async Task When TextBox Text Changed Then Restart It

I've .NET app and there are DataGridView and TextBox on my GUI. What I want to do is when user change TextBox text, update DataGridView where cells contains this text. But this search should run as async task because if it's not, it causes freeze on GUI. Everytime when user changed TextBox text, my app should cancel if another search task running and rerun it again to search according to new search values. Here is my code;
CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)
{
cts.Cancel();
CancellationToken ct = cts.Token;
try
{
await Task.Run(() =>
{
System.Diagnostics.Debug.WriteLine("Task started");
// Searching here.
System.Diagnostics.Debug.WriteLine("Task finished");
}, cts.Token);
}
catch
{
System.Diagnostics.Debug.WriteLine("Cancelled");
}
}
On my code, tasks are canceled without it's started. I only see "Cancelled" line on debug console. I should cancel tasks because if I don't their numbers and app's CPU usage increases. Is there way to do that ?
Like Rand Random said, i should decleare new CancellationTokenSource object. I have edited my code like this and it's worked. Code should be like that:
CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)
{
cts.Cancel();
cts.Dispose();
cts = new CancellationTokenSource();
try
{
await Task.Run(() =>
{
System.Diagnostics.Debug.WriteLine("Task started");
// Searching here.
System.Diagnostics.Debug.WriteLine("Task finished");
}, cts.Token);
}
catch
{
System.Diagnostics.Debug.WriteLine("Cancelled");
}
}
If you want to make life easy for yourself, you should consider using Microsoft's Reactive Framework (aka Rx)
I'm assuming you can write this method:
async Task<string[]> DoSearchAsync(string text, CancellationToken ct)
{
/* you implement this method */
}
Then you can do this:
private IDisposable _searchSubscription = null;
private void Form1_Load(object sender, EventArgs e)
{
_searchSubscription =
Observable
.FromEventPattern(h => TextBox1.TextChanged += h, h => TextBox1.TextChanged -= h)
.Throttle(TimeSpan.FromMilliseconds(400.0))
.Select(ep => TextBox1.Text)
.Select(text => Observable.FromAsync(ct => DoSearchAsync(text, ct)))
.Switch()
.ObserveOn(TextBox1)
.Subscribe(results => { /* update your UI */ });
}
Now this watches your TextChanged event, waits 400.0 milliseconds in case you type another character - there's no sense firing off a load of searches if the user is still typing - and then it calls your new DoSearchAsync method.
Then it does a Switch which effectively means cancel any in-flight searchs and start this new one.
Finally it marshalls back to the UI thread and then gives you the results of the search so that you can update the UI.
It just handles all of the calling, background thread calls, marshalling back to the UI, all for you.
If you want to shut it down, just call _searchSubscription.Dispose().
NuGet System.Reactive.Windows.Forms and add using System.Reactive.Linq; to get the bits.

Show and Close form from a background thread

I have a windows form (AlertForm) that displays a progress bar. I am trying to show it an close it thru a task. The problem that I am having is when I spawn a thread and call winforms.showdialog() it holds the thread and therefore cant cancel it. I am doing this because I am writing an excel add in thru c# in which I don't have a panel to show a progress bar.
static void Main(string[] args)
{
AlertForm alert = new AlertForm();
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task.Factory.StartNew(() =>
{
//while (true)
//{
// do some heavy work here
alert.ShowDialog();
Thread.Sleep(100);
if (ct.IsCancellationRequested)
{
alert.Close();
// another thread decided to cancel
Console.WriteLine("task canceled");
//break;
}
//}
}, ct);
// Simulate waiting 3s for the task to complete
Thread.Sleep(3000);
// Can't wait anymore => cancel this task
ts.Cancel();
Console.ReadLine();
}
How do I open the form and not hold it so at a later stage when a long task is complete I can cancel the task which will close the window
(AlertForm)?
If the AlertForm class is inheritance from Windows.Forms.Control then you can use Invoke method
alert.Invoke((MethodInvoker)delegate ()
{
alert.Close();
});

How to close a form (used from a background thread) in thread safe manner?

I have a form that shows a data grid. I also have a method running on a different thread that updates only the displayed cells of the grid. To do this, this method calls a function on the form that returns the displayed cells.
The problem I have is that sometimes while the form has been closed and disposed the method on the other thread is still calling this function which results in an objectdisposed exception. Is there a way (other then making sure the methode on the other thread is finished) to prevent this?
So I need a thread safe method to kill the background task when the form is closed.
private delegate List<foo> GetShownCellsDelegate();
public List<foo> GetShownCells()
{
if (this.InvokeRequired)
{
GetShownCellsDelegate getShownCellsDelegate = new GetShownCellsDelegate(GetShownCells);
return (List<foo>)this.Invoke(getShownCellsDelegate);
}
else
{
//do stuff
}
}
I tries using the IsDisposed property of the form:
if (!IsDisposed)
{
return (List<foo>)this.Invoke(getShownCellsDelegate);
}
But apparently the form can be dispossed after the if statement because I still get the isdisposed exception.
This is how I use the function on the other thread:
private CancellationTokenSource cts = new CancellationTokenSource();
public void CancelUpdate()
{
cts.Cancel();
}
public void ReadDataFromDevice()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ReadAllDataThreadPoolMethod));
}
private void ReadAllDataThreadPoolMethod(Object stateInfo)
{
if (!cts.IsCancellationRequested)
{
//do stuff
}
}
The CancelUpdate method is called from the IsClosing event on the form. But I still get the isdisposed exception sometimes.
To cancel the long running operation you can use a CancellationToken, which is specifically designed for cooperative cancellation.
Have the main form create a CancellationTokenSource when starting the background thread, pass the CacellationToken generated by the CTS to the backround thread, cancel the CTS when your form closes, and then have the background thread check the token to see if it is cancelled before trying to invoke back to the main thread.
public void Foo()
{
var cts = new CancellationTokenSource();
var task = Task.Run(() => DoWork(cts.Token));
FormClosing += (s, args) =>
{
cts.Cancel();
if (!task.IsCompleted)
{
args.Cancel = true;
task.ContinueWith(t => Close());
}
};
}
private void DoWork(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
//Do some work
}
}
To be absolutely sure that the background thread doesn't pass the cancellation check, then yield to the UI thread to have it cancel the token and dispose of the form, before the work is done, you'll also need to ensure that the background thread has time to run to completion after being cancelled, before the form closes. This can be done through a simple Thread.Join call in the closing handler.
this.FormClosed += new FormClosedEventHandler(form1_FormClosed);
void form1_FormClosed(object sender, FormClosedEventArgs e)
{
//close thread
}
This will be executed whenever your form is being closed.

Why is Window.ShowDialog not blocking in TaskScheduler Task?

I'm using a custom TaskScheduler to execute a task queue in serial. The task is supposed to display a window and then block until the window closes itself. Unfortunately calling Window.ShowDialog() doesn't seem to block so the task completes and the window never displays.
If I put a breakpoint after the call to ShowDialog I can see the form has opened but under normal execution the Task seems to end so quickly you cant see it.
My TaskScheduler implementation taken from a previous question:
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
private readonly List<Thread> threads;
private BlockingCollection<Task> tasks;
public override int MaximumConcurrencyLevel
{
get { return threads.Count; }
}
public StaTaskScheduler(int concurrencyLevel)
{
if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
this.tasks = new BlockingCollection<Task>();
this.threads = Enumerable.Range(0, concurrencyLevel).Select(i =>
{
var thread = new Thread(() =>
{
foreach (var t in this.tasks.GetConsumingEnumerable())
{
this.TryExecuteTask(t);
}
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
this.threads.ForEach(t => t.Start());
}
protected override void QueueTask(Task task)
{
tasks.Add(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return tasks.ToArray();
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && TryExecuteTask(task);
}
public void Dispose()
{
if (tasks != null)
{
tasks.CompleteAdding();
foreach (var thread in threads) thread.Join();
tasks.Dispose();
tasks = null;
}
}
}
My Application Code:
private StaTaskScheduler taskScheduler;
...
this.taskScheduler = new StaTaskScheduler(1);
Task.Factory.StartNew(() =>
{
WarningWindow window = new WarningWindow(
ProcessControl.Properties.Settings.Default.WarningHeader,
ProcessControl.Properties.Settings.Default.WarningMessage,
processName,
ProcessControl.Properties.Settings.Default.WarningFooter,
ProcessControl.Properties.Settings.Default.WarningTimeout * 1000);
window.ShowDialog();
}, CancellationToken.None, TaskCreationOptions.None, this.taskScheduler);
Nothing obviously wrong. Except what is missing, you are not doing anything to ensure that an exception that's raised in the task is reported. The way you wrote it, such an exception will never be reported and you'll just see code failing to run. Like a dialog that just disappears. You'll need to write something like this:
Task.Factory.StartNew(() => {
// Your code here
//...
}, CancellationToken.None, TaskCreationOptions.None, taskScheduler)
.ContinueWith((t) => {
MessageBox.Show(t.Exception.ToString());
}, TaskContinuationOptions.OnlyOnFaulted);
With good odds that you'll now see an InvalidOperationException reported. Further diagnose it with Debug + Exceptions, tick the Thrown checkbox for CLR exceptions.
Do beware that this task scheduler doesn't magically makes your code thread-safe or fit to run another UI thread. It wasn't made for that, it should only be used to keep single-threaded COM components happy. You must honor the sometimes draconian consequences of running UI on another thread. In other words, don't touch properties of UI on the main thread. And the dialog not acting like a dialog at all since it doesn't have an owner. And it thus randomly disappearing behind another window or accidentally getting closed by the user because he was clicking away and never counted on a window appearing from no-where.
And last but not least the long-lasting misery caused by the SystemEvents class. Which needs to guess which thread is the UI thread, it will pick the first STA thread. If that's your dialog then you'll have very hard to diagnose threading problems later.
Don't do it.
Apparently, you're using Stephen Toub's StaTaskScheduler. It is not intended to run tasks involving the UI. Essentially, you're trying to display a modal window with window.ShowDialog() on a background thread which has nothing to do with the main UI thread. I suspect the window.ShowDialog() instantly finishes with an error, and so does the task. Await the task and observe the errors:
try
{
await Task.Factory.StartNew(() =>
{
WarningWindow window = new WarningWindow(
ProcessControl.Properties.Settings.Default.WarningHeader,
ProcessControl.Properties.Settings.Default.WarningMessage,
processName,
ProcessControl.Properties.Settings.Default.WarningFooter,
ProcessControl.Properties.Settings.Default.WarningTimeout * 1000);
window.ShowDialog();
}, CancellationToken.None, TaskCreationOptions.None, this.taskScheduler);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message)
}
If you really want to show a window from a background STA thread, you need to run a Dispatcher loop:
var task = Task.Factory.StartNew(() =>
{
System.Windows.Threading.Dispatcher.InvokeAsync(() =>
{
WarningWindow window = new WarningWindow(
ProcessControl.Properties.Settings.Default.WarningHeader,
ProcessControl.Properties.Settings.Default.WarningMessage,
processName,
ProcessControl.Properties.Settings.Default.WarningFooter,
ProcessControl.Properties.Settings.Default.WarningTimeout * 1000);
window.Closed += (s, e) =>
window.Dispatcher.InvokeShutdown();
window.Show();
});
System.Windows.Threading.Dispatcher.Run();
}, CancellationToken.None, TaskCreationOptions.None, this.taskScheduler);
Note however, this window will not be modal as to any main UI thread window. Moreover, you'd block the StaTaskScheduler loop, so its other scheduled tasks won't run until the window is closed and Dispatcher.Run() exits.
I had a similar problem where on start up I could not manage to block execution via a ShowDialog. Eventually I discovered that inside the class properties were Command Line Arguments that was used to automatically log in with appropriate credentials. This saved time during development not to enter a username and password every time you compile. As soon as I removed that the Dialog behaved as expected so it's worth while to search for processes that might interfere with the normal execution path.

Calling method on correct thread when control is created in new Thread()

I've created a new WebBrowser() control in a new Thread().
The problem I'm having, is that when invoking a delegate for my WebBrowser from the Main Thread, the call is occurring on the Main Thread. I would expect this to happen on browserThread.
private static WebBrowser defaultApiClient = null;
delegate void DocumentNavigator(string url);
private WebApi() {
// Create a new thread responsible
// for making API calls.
Thread browserThread = new Thread(() => {
defaultApiClient = new WebBrowser();
// Setup our delegates
documentNavigatorDelegate = new DocumentNavigator(defaultApiClient.Navigate);
// Anonymous event handler
defaultApiClient.DocumentCompleted += (object sender, WebBrowserDocumentCompletedEventArgs e) => {
// Do misc. things
};
Application.Run();
});
browserThread.SetApartmentState(ApartmentState.STA);
browserThread.Start();
}
DocumentNavigator documentNavigatorDelegate = null;
private void EnsureInitialized() {
// This always returns "false" for some reason
if (defaultApiClient.InvokeRequired) {
// If I jump ahead to this call
// and put a break point on System.Windows.Forms.dll!System.Windows.Forms.WebBrowser.Navigate(string urlString, string targetFrameName, byte[] postData, string additionalHeaders)
// I find that my call is being done in the "Main Thread".. I would expect this to be done in "browserThread" instead
object result = defaultApiClient.Invoke(documentNavigatorDelegate, WebApiUrl);
}
}
I've tried invoking the method a myriad of ways:
// Calls on Main Thread (as expected)
defaultApiClient.Navigate(WebApiUrl);
// Calls on Main Thread
defaultApiClient.Invoke(documentNavigatorDelegate, WebApiUrl);
// Calls on Main Thread
defaultApiClient.BeginInvoke(documentNavigatorDelegate, WebApiUrl);
// Calls on Main Thread
documentNavigatorDelegate.Invoke(WebApiUrl);
// Calls on random Worker Thread
documentNavigatorDelegate.BeginInvoke(WebApiUrl, new AsyncCallback((IAsyncResult result) => { .... }), null);
Update
Let me break down my end-goal a little bit to make things more clear: I have to make calls using WebBrowser.Document.InvokeScript(), however Document is not loaded until after I call WebBrowser.Navigate() and THEN the WebBrowser.DocumentComplete event fires. Essentially, I cannot make my intended call to InvokeScript() until after DocumentComplete fires... I would like to WAIT for the document to load (blocking my caller) so I can call InvokeScript and return my result in a synchronous fashion.
Basically I need to wait for my document to complete and the way I would like to do that is with a AutoResetEvent() class which I will trigger upon DocumentComplete being fired... and I need all this stuff to happen in a separate thread.
The other option I see is doing something like this:
private bool initialized = false;
private void EnsureInitialized(){
defaultApiClient.Navigate(WebApiUrl);
while(!initialized){
Thread.Sleep(1000); // This blocks so technically wouldn't work
}
}
private void defaultApiClient_DocumentComplete(object sender, WebBrowserDocumentCompletedEventArgs e){
initialized = true;
}
This is by design. The InvokeRequired/BeginInvoke/Invoke members of a control require the Handle property of the control to be created. That is the primary way by which it can figure out to what specific thread to invoke to.
But that did not happen in your code, the Handle is normally only created when you add a control to a parent's Controls collection and the parent was displayed with Show(). In other words, actually created the host window for the browser. None of this happened in your code so Handle is still IntPtr.Zero and InvokeRequired returns false.
This is not actually a problem. The WebBrowser class is special, it is a COM server under the hood. COM handles threading details itself instead of leaving it up to the programmer, very different from the way .NET works. And it will automatically marshal a call to its Navigate() method. This is entirely automatic and doesn't require any help. A hospitable home for the COM server is all that's needed, you made one by creating an STA thread and pumping a message loop with Application.Run(). It is the message loop that COM uses to do the automatic marshaling.
So you can simply call Navigate() on your main thread and nothing goes wrong. The DocumentCompleted event still fires on the helper thread and you can take your merry time tinkering with the Document on that thread.
Not sure why any of this is a problem, it should work all just fine. Maybe you were just mystified about its behavior. If not then this answer could help you with a more universal solution. Don't fear the nay-sayers too much btw, displaying UI on a worker thread is filled with traps but you never actually display any UI here and never create a window.
This answer is based on the updated question and the comments:
Basically I need to wait for my document to complete and the way I
would like to do that is with a AutoResetEvent() class which I will
trigger upon DocumentComplete being fired... and I need all this stuff
to happen in a separate thread.
...
I am aware that the main UI will be frozen. This will happen only once
during the lifetime of the application (upon initialization). I'm
struggling to find another way to do what I'm looking to accomplish.
I don't think you should be using a separate thread for this. You could disable the UI (e.g. with a modal "Please wait..." dialog) and do the WebBrowser-related work on the main UI thread.
Anyhow, the code below shows how to drive a WebBrowser object on a separate STA thread. It's based on the related answer I recently posted, but is compatible with .NET 4.0. With .NET 4+, you no longer need to use low-level synchronization primitives like AutoResetEvent. Use TaskCompletionSource instead, it allows to propagate the result and possible exceptions to the consumer side of the operation.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinFroms_21790151
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
this.Load += MainForm_Load;
}
void MainForm_Load(object senderLoad, EventArgs eLoad)
{
using (var apartment = new MessageLoopApartment())
{
// create WebBrowser on a seprate thread with its own message loop
var webBrowser = apartment.Invoke(() => new WebBrowser());
// navigate and wait for the result
var bodyHtml = apartment.Invoke(() =>
{
WebBrowserDocumentCompletedEventHandler handler = null;
var pageLoadedTcs = new TaskCompletionSource<string>();
handler = (s, e) =>
{
try
{
webBrowser.DocumentCompleted -= handler;
pageLoadedTcs.SetResult(webBrowser.Document.Body.InnerHtml);
}
catch (Exception ex)
{
pageLoadedTcs.SetException(ex);
}
};
webBrowser.DocumentCompleted += handler;
webBrowser.Navigate("http://example.com");
// return Task<string>
return pageLoadedTcs.Task;
}).Result;
MessageBox.Show("body content:\n" + bodyHtml);
// execute some JavaScript
var documentHtml = apartment.Invoke(() =>
{
// at least one script element must be present for eval to work
var scriptElement = webBrowser.Document.CreateElement("script");
webBrowser.Document.Body.AppendChild(scriptElement);
// inject and run some script
var scriptResult = webBrowser.Document.InvokeScript("eval", new[] {
"(function(){ return document.documentElement.outerHTML; })();"
});
return scriptResult.ToString();
});
MessageBox.Show("document content:\n" + documentHtml);
// dispose of webBrowser
apartment.Invoke(() => webBrowser.Dispose());
webBrowser = null;
}
}
// MessageLoopApartment
public class MessageLoopApartment : IDisposable
{
Thread _thread; // the STA thread
TaskScheduler _taskScheduler; // the STA thread's task scheduler
public TaskScheduler TaskScheduler { get { return _taskScheduler; } }
/// <summary>MessageLoopApartment constructor</summary>
public MessageLoopApartment()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
// start an STA thread and gets a task scheduler
_thread = new Thread(startArg =>
{
EventHandler idleHandler = null;
idleHandler = (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return the task scheduler
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
_taskScheduler = tcs.Task.Result;
}
/// <summary>shutdown the STA thread</summary>
public void Dispose()
{
if (_taskScheduler != null)
{
var taskScheduler = _taskScheduler;
_taskScheduler = null;
// execute Application.ExitThread() on the STA thread
Task.Factory.StartNew(
() => Application.ExitThread(),
CancellationToken.None,
TaskCreationOptions.None,
taskScheduler).Wait();
_thread.Join();
_thread = null;
}
}
/// <summary>Task.Factory.StartNew wrappers</summary>
public void Invoke(Action action)
{
Task.Factory.StartNew(action,
CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait();
}
public TResult Invoke<TResult>(Func<TResult> action)
{
return Task.Factory.StartNew(action,
CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
}
public Task Run(Action action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
public Task<TResult> Run<TResult>(Func<TResult> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
public Task Run(Func<Task> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
public Task<TResult> Run<TResult>(Func<Task<TResult>> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
}
}
}

Categories

Resources