TaskScheduler.Current and TaskScheduler.FromCurrentSynchronizationContext() difference? - c#

I have a task to get products from database, and the ContinueWith action that operate some UI modification, therefore I had a problem because the Task create a new thread, and the UI modification was executed not in the UI Thread.
I tried to use this fix :
var currentScheduler = TaskScheduler.Current;
Task.Factory.StartNew(() =>
{
// get products
}).ContinueWith((x) => handleProductsArrived(x.Result, x.Exception), currentScheduler);
but it didn't work at all. I check and the ContinueWith was not executed in the thread from currentScheduler but in an another.
I discovered this method :
Task.Factory.StartNew(() =>
{
// get products
}).ContinueWith((x) => handleProductsArrived(x.Result, x.Exception), TaskScheduler.FromCurrentSynchronizationContext());
and it works. So what's the differences? Why didn't my first code work?
Thanks!

From the documentation for TaskScheduler.Current:
When not called from within a task, Current will return the Default scheduler.
Then from the Task Schedulers documentation:
The default scheduler for Task Parallel Library and PLINQ uses the .NET Framework ThreadPool to queue and execute work.
So if you use TaskScheduler.Current when you're not in a task, you'll get a scheduler which uses the thread pool.
If you call TaskScheduler.FromCurrentSynchronizationContext(), you'll get one for the current synchronization context - which in Windows Forms or WPF (when called from a UI thread) is a context which schedules work on the relevant UI thread.
So that's why the first code didn't work: it executed your continuation on a thread pool thread. Your second code executed the continuation on the UI thread.
Note that if you can use C# 5 and async/await, all of this is handled much more simply.

Related

Task.Factory.StartNew and Application.Current.Dispatcher.Invoke

Given this code:
Task.Factory.StartNew(() =>
{
Application.Current.Dispatcher.Invoke(() =>
{
//Code to run on UI thread
});
//Code to run on a background thread
}).
Is it safe to assume that "Code to run on a background thread" will not be reached under any circumstances until "Code to run on UI thread" is finished executing?
When you start the Task, it will run as an worker thread and it will block until Invoke(...) is over.
When Invoke(..) is done it will continue on the worker thread.
"Code to run on UI thread" will run first, next will be "Code to run on a background thread".
You then have the possibility to do some work in a worker thread like read files, write files, query on database with out blocking the main thread, and when needed you can update UI elements with data via Invoke(...)
But take a look at Async/Await, it can make async operation and updating UI element easier to understand.
Dispatcher.Invoke Method (Delegate, Object[])
Executes the specified delegate with the specified arguments synchronously on the thread the Dispatcher is associated with.
https://msdn.microsoft.com/en-us/library/cc647509(v=vs.110).aspx
also see this:
Asynchronous vs synchronous execution, what does it really mean?
Because Dispatcher.Invoke is executed synchronously, and how synchronous execution is defined, your assumption is safe.

Execute Task.Run on main thread

I am using monotouch/Xamarin for an iOS app.
The documentation for Task.Run states:
Queues the specified work to run on the ThreadPool and returns a task
handle for that work.
Which essentially indicates that it could run on any thread ThreadPool.
I want to do something like:
Task.Run(async () => await PerformTask());
but have it run on the main thread. Normally I would write it using BeginInvokeOnMainThread as follows:
BeginInvokeOnMainThread(async () => await PerformTask());
But I am doing this in shared code and do not want to use iOS specific calls. Is there a way for me to tell Task.Run() to invoke the action on the main thread?
If you want to run PerformTask in the current thread, instead of a thread pool thread, you simply need to not call Task.Run. Just use:
PerformTask();
and you're done. If you want to only continue executing the rest of the method when that task is done, then await it:
await PerformTask();
There is no reason to call Task.Run here for you, nor is there any reason to create a lambda that awaits the method for no particular reason (you could just call it directly if you wanted to start it from a thread pool thread).
If you want to run a task from the main thread you could use TaskSchedluer's method FromCurrentSynchronizationContext().
Task t = new Task(() =>
{
...
});
t.Start(TaskScheduler.FromCurrentSynchronizationContext());
This can be useful sometimes. However, if you want to use this technique to bypass thread checks (for example, modifying properties of form controls in a windows app), you should rethink your design. Use tasks for calculations or other non-UI operations and update the UI from the main thread.
Have a look at MainQueue.cs: https://gist.github.com/gering/0aa9750d3c7d14b856d0ed2ba98374a8
It is for Xamarin Forms applications. You have to call Init() from main thread once, but then you are able to ensure execution on main thread.

Thread killing by name

I have a problem with my thread...
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate {}));
Thread.Sleep(90);
It starts and works fine but like forever, and I don't want to run this thread forever.
Is there possible way to give to this thread a name so I can kill it by name in any time I want?
I tried kill it with:
Dispatcher.CurrentDispatcher.thread.Abort();
but it kill's whole app...
Basically...
I have a custom combo in my WPF app... this thread is in while loop, when I open the combo starts a loop while(!context.IsClosed) but when its closed, it still runs in background
Your understanding of multithreading approach is completely wrong.
First of all, no, there is no way to give a name to your thread being invoked in such way.
Second, killing a thread is a completely wrong approach in the situations like this, there is easy way to do such things: CancellationToken. You can use some overloads for the Dispatcher.Invoke with them (either using the start timeout or not), like this:
Dispatcher.Invoke Method (Action, DispatcherPriority, CancellationToken):
CancellationTokenSource s = new CancellationTokenSource();
Dispatcher.CurrentDispatcher.Invoke(() => YourMethodHere(), DispatcherPriority.Background, s.Token);
Thread.Sleep(90);
s.Cancel();
After calling the Cancel method the .NET will automatically stop your thread.
Second possible approach, as written in comments, is to use TPL for this, without using the Thread creation, something like this (code from MSDN article about SynchronizationContext):
// This TaskScheduler captures SynchronizationContext.Current.
TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
// Start a new task (this uses the default TaskScheduler,
// so it will run on a ThreadPool thread).
Task.Factory.StartNew(() =>
{
// We are running on a ThreadPool thread here.
// Do some work.
// Report progress to the UI.
Task reportProgressTask = Task.Factory.StartNew(() =>
{
// We are running on the UI thread here.
// Update the UI with our progress.
},
s.Token,
TaskCreationOptions.None,
taskScheduler);
reportProgressTask.Wait();
// Do more work.
});

Task is being scheduled on the same thread as the caller's

My application has a View Model which contains a Lazy<BitmapImage> field. The field is populated using a service call to the server. In cases where the image is large, it takes a few seconds for the server to return the image (which is in fact a byte[]) therefore the UI is blocked. To prevent this, I put the service call in a Task, so that a background thread gets the image and then calls the OnPropertyChanged to let the UI know the image is returned:
Console.WriteLine("Outside Task ThreadID: {0}",
Thread.CurrentThread.ManagedThreadId);
Task.Factory.StartNew(() =>
{
Console.WriteLine("Inside Task ThreadID: {0}", Thread.CurrentThread.ManagedThreadId);
return Utilities.ConvertByteToImage(
SessionService.GetUserInformation(UserInfo.From).ProfilePicture);
}).ContinueWith(resultToken =>
{
m_lazyProfilePicture = new Lazy<BitmapImage>(() =>
{
return (resultToken.Result == null) ? Utilities.DefaultProfilePicture.Value : resultToken.Result;
});
OnPropertyChanged("ProfilePicture");
});
I noticed that even after putting the service call in a Task, the UI is till blocked. So added those Console.WriteLine lines to see the thread IDs. Surprisingly enough, both of them report the same thread ID (this seems to happen only in this case.I tried it with other tasks in the project, and they all report different IDs). Any idea what's going on here? Does it have anything to do with the BitmapImage? For some reason the scheduler decides to put the task in the same thread, but I don't understand why. Any suggestions are welcome!
StartNew doesn't ensure that the task is run in a new thread. It uses TaskScheduler.Current to schedule the new task. In many places throughout your code this will be null. When it is null, then TaskScheduler.Default will be used, which will schedule the delegate to run in the thread pool.
In your particular case Current is not null. It is the representation of some task scheduler that schedules the delegates to run in the UI thread.
One way this may have happened is if the code that you are running is the result of a call to StartNew or ContinueWith with the UI synchronization context. During the delegates executed in either case it will set the current scheduler to be one that is based on the SynchronizationContext provided, namely the UI context.
If you use Task.Run you avoid the issue; it will always use the default task scheduler instead of the current one.
Your other option is to explicitly state you want the default task scheduler:
Task.Factory.StartNew(() => { }
, CancellationToken.None
, TaskCreationOptions.None
, TaskScheduler.Default);

Sleep task (System.Threading.Tasks)

I need to create thread which will replace photo in Windows Forms window, than waits for ~1second and restore the previous photo.
I thought that the following code:
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
Thread.Sleep(1000);
pic.Image = Properties.Resources.PREV;
}, CancellationToken.None, TaskCreationOptions.LongRunning, ui)
do the job, but unfortunately doesn't. It freezes main UI thread.
That's because it's not guaranteed that there is one thread per one task. One thread can be used for processing several tasks.
Even TaskCreationOptions.LongRunning option can't help.
How I can fix it?
Thread.Sleep is a synchronous delay. If you want an asynchronous delay then use Task.Delay.
In C# 5, which is at present in beta release, you can simply say
await Task.Delay(whatever);
in an asynchronous method, and the method will automatically pick up where it left off.
If you are not using C# 5 then you can "manually" set whatever code you want to be the continuation of the delay yourself.
When you pass a new TaskScheduler that is from the current synchronization context, you actually telling the task to run on the UI thread. You actually want to do that, so you can update the UI component, however you don't want to sleep on that thread, since it will block.
This is a good example of when .ContinueWith is ideal:
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
EDIT (Removed some stuff and added this):
What happens is that we're blocking the UI thread for only enough time to update pic.Image. By specifying the TaskScheduler, you're telling it what thread to run the task on. It's important to know that the relationship between Tasks and Threads is not 1:1. In fact, you can have 1000 tasks running on relatively few threads, 10 or less even, it all depends on the amount of work each task has. Do not assume each task you create will run on a separate thread. The CLR does a great job of balancing performance automatically for you.
Now, you don't have to use the default TaskScheduler, as you've seen. When you pass the UI TaskScheduler, that is TaskScheduler.FromCurrentSynchronizationContext(), it uses the UI thread instead of the thread pool, as TaskScheduler.Default does.
Keeping this in mind, let's review the code again:
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
Here, we're creating and starting a task that will run on the UI thread, that will update the Image property of pic with your resource. While it does this, the UI will be unresponsive. Fortunately, this is a likely a very fast operation, and the user won't even notice.
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
With this code, we're calling the ContinueWith method. It does exactly what it sounds like. It returns a new Task object that will execute the lambda parameter when it runs. It will be started when the task has either completed, faulted or been cancelled. You can control when it will run by passing in TaskContinuationOptions. However, we're also passing a different task scheduler as we did before. This is the default task scheduler that will execute a task on a thread pool thread, thus, NOT blocking the UI. This task could run for hours and your UI will stay responsive (don't let it), because it's a separate thread from the UI thread that you are interacting with.
We've also called ContinueWith on the tasks we've set to run on the default task scheduler. This is the task that will update the image on the UI thread again, since we've passed that same UI task scheduler to the executing task. Once the threadpool task has finished, it will call this one on the UI thread, blocking it for a very short period of time while the image is updated.
You should be using a Timer to perform a UI task at some point in the future. Just set it to run once, and with a 1 second interval. Put the UI code in the tick event and then set it off.
If you really wanted to use tasks, you'd want to have the other task not run in the UI thread but rather in a background threat (i.e. just a regular StartNew task) and then use the Control.Invoke inside of the task to run a command on the UI thread. The problem here is that is' band-aid-ing the underlying problem of starting a task just to have it sleep. Better to just have the code not even execute in the first place for the full second.

Categories

Resources