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

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);

Related

Why this async method don't cause deadlock on a thread?

I need an explanation why this code doesn't cause deadlock on the thread it's running in
(It's a WinForm application and these happens in button_Click):
Task FakeMainThread = Task.Run(async() => // fake main thread
{
async Task asyncMethod() // executed in FakeMainThread
{
await Task.Run(() => // 'await'ing in FakeMainThread
{
Thread.Sleep(5000);
MessageBox.Show("child Task finished");
return 3;
});
MessageBox.Show("asyncMethod finished");
}
asyncMethod().Wait(); // deadlock should appear in FakeMainThread
MessageBox.Show("FakeMainThread finished");
return 4;
});
asyncMethod().Wait(); should block the fakeMainThread Task's thread while it's await another task to finish and since await is happening in fakeMainThread too it shouldn't be able to 'await' and deadlock should appear on fakeMainThread. But it doesn't happen and I see bot MessageBox.Show("asyncMethod finished"); and MessageBox.Show("FakeMainThread finished"); messages.
Note: if I put this:
async Task asyncMethod()
{
await Task.Run(() =>
{
Thread.Sleep(5000);
MessageBox.Show("child Task finished");
return 3;
});
MessageBox.Show("asyncMethod finished");
}
asyncMethod().Wait(); // deadlock appears in Main Thread (UI)
MessageBox.Show("FakeMainThread finished");
in main button1_click() inside directli deadlock appears on UI. Thanks!
You don't experience a deadlock in the first code example because of the SynchronizationContext. This context says what the await must do to restore your code. When you start a new task (Task.Run), you will get the default context. In the button1_click you get the context from the form.
The context for the form allows to execute only a single thread (the one used to paint/update your form).
When you call .Wait(), then you keep that thread 'locked' with waiting until the task is done. That means that the thread is not released for another job and this is one of the reasons that the form goes into the 'not responding' state.
When you do an await [code], and that code is done, then it will ask the synchronization context to schedule the remainder of the code.
In case of the default context, any free thread is taken and your code will continue, the task is marked as done, and thus the 'loop' in the .Wait() gets signaled that the task is done and continues.
In case of the forms context, there is only a single thread, thus the completion of this task is scheduled to run after the current code is done. This is the reason of the deadlock.
Avoiding this deadlock is the main reason that you most often get the suggestion to use ConfigureAwait(false), this tells the await that it should substitute the current synchronization context with the default one, thus allowing you to use any free thread.
The only reason you don't want to use this (or say ConfigureAwait(true)) is if you need to update something on your form (WPF or WinForms) or you need your http context (ASP.NET, not ASP.NET Core). This is because only a single thread has access to your form or http context. Here you don't get an exception on your MessageBox.Show because this is 'outside' of the forms context and doesn't need this special thread/synchronization context.
See https://devblogs.microsoft.com/dotnet/configureawait-faq/ for more information.

In Unity / C#, does .Net's async/await start, literally, another thread?

Important for anyone researching this difficult topic in Unity specifically,
be sure to see another question I asked which raised related key issues:
In Unity specifically, "where" does an await literally return to?
For C# experts, Unity is single-threaded1
It's common to do calculations and such on another thread.
When you do something on another thread, you often use async/wait since, uh, all the good C# programmers say that's the easy way to do that!
void TankExplodes() {
ShowExplosion(); .. ordinary Unity thread
SoundEffects(); .. ordinary Unity thread
SendExplosionInfo(); .. it goes to another thread. let's use 'async/wait'
}
using System.Net.WebSockets;
async void SendExplosionInfo() {
cws = new ClientWebSocket();
try {
await cws.ConnectAsync(u, CancellationToken.None);
...
Scene.NewsFromServer("done!"); // class function to go back to main tread
}
catch (Exception e) { ... }
}
OK, so when you do this, you do everything "just as you normally do" when you launch a thread in a more conventional way in Unity/C# (so using Thread or whatever or letting a native plugin do it or the OS or whatever the case may be).
Everything works out great.
As a lame Unity programmer who only knows enough C# to get to the end of the day, I have always assumed that the async/await pattern above literally launches another thread.
In fact, does the code above literally launch another thread, or does c#/.Net use some other approach to achieve tasks when you use the natty async/wait pattern?
Maybe it works differently or specifically in the Unity engine from "using C# generally"? (IDK?)
Note that in Unity, whether or not it is a thread drastically affects how you have to handle the next steps. Hence the question.
Issue: I realize there's lots of discussion about "is await a thread", but, (1) I have never seen this discussed / answered in the Unity setting (does it make any difference? IDK?) (2) I simply have never seen a clear answer!
1 Some ancillary calculations (eg, physics etc) are done on other threads, but the actual "frame based game engine" is one pure thread. (It's impossible to "access" the main engine frame thread in any way whatsoever: when programming, say, a native plugin or some calculation on another thread, you just leave markers and values for the components on the engine frame thread to look at and use when they run each frame.)
This reading: Tasks are (still) not threads and async is not parallel might help you understand what's going on under the hood.
In short in order for your task to run on a separate thread you need to call
Task.Run(()=>{// the work to be done on a separate thread. });
Then you can await that task wherever needed.
To answer your question
"In fact, does the code above literally launch another thread, or does
c#/.Net use some other approach to achieve tasks when you use the
natty async/wait pattern?"
No - it doesn't.
If you did
await Task.Run(()=> cws.ConnectAsync(u, CancellationToken.None));
Then cws.ConnectAsync(u, CancellationToken.None) would run on a separate thread.
As an answer to the comment here is the code modified with more explanations:
async void SendExplosionInfo() {
cws = new ClientWebSocket();
try {
var myConnectTask = Task.Run(()=>cws.ConnectAsync(u, CancellationToken.None));
// more code running...
await myConnectTask; // here's where it will actually stop to wait for the completion of your task.
Scene.NewsFromServer("done!"); // class function to go back to main tread
}
catch (Exception e) { ... }
}
You might not need it on a separate thread though because the async work you're doing is not CPU bound (or so it seems). Thus you should be fine with
try {
var myConnectTask =cws.ConnectAsync(u, CancellationToken.None);
// more code running...
await myConnectTask; // here's where it will actually stop to wait for the completion of your task.
Scene.NewsFromServer("done!"); // continue from here
}
catch (Exception e) { ... }
}
Sequentially it will do exactly the same thing as the code above but on the same thread. It will allow the code after "ConnectAsync" to execute and will only stop to wait for the completion of "ConnectAsync" where it says await and since "ConnectAsync" is not CPU bound you (making it somewhat parallel in a sense of the work being done somewhere else i. e. networking) will have enough juice to run your tasks on, unless your code in "...." also requires a lot of CPU bound work, that you'd rather run in parallel.
Also you might want to avoid using async void for it's there only for top level functions. Try using async Task in your method signature. You can read more on this here.
No, async/await does not mean - another thread. It can start another thread but it doesn't have to.
Here you can find quite interesting post about it: https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/
Important notice
First of all, there's an issue with your question's first statement.
Unity is single-threaded
Unity is not single-threaded; in fact, Unity is a multi-threaded environment. Why? Just go to the official Unity web page and read there:
High-performance multithreaded system: Fully utilize the multicore processors available today (and tomorrow), without heavy programming. Our new foundation for enabling high-performance is made up of three sub-systems: the C# Job System, which gives you a safe and easy sandbox for writing parallel code; the Entity Component System (ECS), a model for writing high-performance code by default, and the Burst Compiler, which produces highly-optimized native code.
The Unity 3D engine uses a .NET Runtime called "Mono" which is multi-threaded by its nature. For some platforms, the managed code will be transformed into native code, so there will be no .NET Runtime. But the code itself will be multi-threaded anyway.
So please, don't state misleading and technically incorrect facts.
What you're arguing with, is simply a statement that there is a main thread in Unity which processes the core workload in a frame-based way. This is true. But it isn't something new and unique! E.g. a WPF application running on .NET Framework (or .NET Core starting with 3.0) has a main thread too (often called the UI thread), and the workload is processed on that thread in a frame-based way using the WPF Dispatcher (dispatcher queue, operations, frames etc.) But all this doesn't make the environment single-threaded! It's just a way to handle the application's logic.
An answer to your question
Please note: my answer only applies to such Unity instances that run a .NET Runtime environment (Mono). For those instances that convert the managed C# code into native C++ code and build/run native binaries, my answer is most probably at least inaccurate.
You write:
When you do something on another thread, you often use async/wait since, uh, all the good C# programmers say that's the easy way to do that!
The async and await keywords in C# are just a way to use the TAP (Task-Asynchronous Pattern).
The TAP is used for arbitrary asynchronous operations. Generally speaking, there is no thread. I strongly recommend to read this Stephen Cleary's article called "There is no thread". (Stephen Cleary is a renowned asynchronous programming guru if you don't know.)
The primary cause for using the async/await feature is an asynchronous operation. You use async/await not because "you do something on another thread", but because you have an asynchronous operation you have to wait for. Whether there is a background thread this operation will run or or not - this does not matter for you (well, almost; see below). The TAP is an abstraction level that hides these details.
In fact, does the code above literally launch another thread, or does c#/.Net use some other approach to achieve tasks when you use the natty async/wait pattern?
The correct answer is: it depends.
if ClientWebSocket.ConnectAsync throws an argument validation exception right away (e.g. an ArgumentNullException when uri is null), no new thread will be started
if the code in that method completes very quickly, the result of the method will be available synchronously, no new thread will be started
if the implementation of the ClientWebSocket.ConnectAsync method is a pure asynchronous operation with no threads involved, your calling method will be "suspended" (due to await) - so no new thread will be started
if the method implementation involves threads and the current TaskScheduler is able to schedule this work item on a running thread pool thread, no new thread will be started; instead, the work item will be queued on an already running thread pool thread
if all thread pool threads are already busy, the runtime might spawn new threads depending on its configuration and current system state, so yes - a new thread might be started and the work item will be queued on that new thread
You see, this is pretty much complex. But that's exactly the reason why the TAP pattern and the async/await keyword pair were introduced into C#. These are usually the things a developer doesn't want to bother with, so let's hide this stuff in the runtime/framework.
#agfc states a not quite correct thing:
"This won't run the method on a background thread"
await cws.ConnectAsync(u, CancellationToken.None);
"But this will"
await Task.Run(()=> cws.ConnectAsync(u, CancellationToken.None));
If ConnectAsync's synchronous part implementation is tiny, the task scheduler might run that part synchronously in both cases. So these both snippets might be exactly the same depending on the called method implementation.
Note that the ConnectAsync has an Async suffix and returns a Task. This is a convention-based information that the method is truly asynchronous. In such cases, you should always prefer await MethodAsync() over await Task.Run(() => MethodAsync()).
Further interesting reading:
await vs await Task.Run
return Task.Run vs await Task.Run
I don't like answering my own question, but as it turns out none of the answers here is totally correct. (However many/all of the answers here are hugely useful in different ways).
In fact, the actual answer can be stated in a nutshell:
On which thread the execution resumes after an await is controlled by SynchronizationContext.Current.
That's it.
Thus in any particular version of Unity (and note that, as of writing 2019, they are drastically changing Unity - https://unity.com/dots) - or indeed any C#/.Net environment at all - the question on this page can be answered properly.
The full information emerged at this follow-up QA:
https://stackoverflow.com/a/55614146/294884
The code after an await will continue on another threadpool thread. This can have consequences when dealing with non-thread-safe references in a method, such as a Unity, EF's DbContext and many other classes, including your own custom code.
Take the following example:
[Test]
public async Task TestAsync()
{
using (var context = new TestDbContext())
{
Console.WriteLine("Thread Before Async: " + Thread.CurrentThread.ManagedThreadId.ToString());
var names = context.Customers.Select(x => x.Name).ToListAsync();
Console.WriteLine("Thread Before Await: " + Thread.CurrentThread.ManagedThreadId.ToString());
var result = await names;
Console.WriteLine("Thread After Await: " + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
The output:
------ Test started: Assembly: EFTest.dll ------
Thread Before Async: 29
Thread Before Await: 29
Thread After Await: 12
1 passed, 0 failed, 0 skipped, took 3.45 seconds (NUnit 3.10.1).
Note that code before and after the ToListAsync is running on the same thread. So prior to awaiting any of the results we can continue processing, though the results of the async operation will not be available, just the Task that is created. (which can be aborted, awaited, etc.)
Once we put an await in, the code following will be effectively split off as a continuation, and will/can come back on a different thread.
This applies when awaiting for an async operation in-line:
[Test]
public async Task TestAsync2()
{
using (var context = new TestDbContext())
{
Console.WriteLine("Thread Before Async/Await: " + Thread.CurrentThread.ManagedThreadId.ToString());
var names = await context.Customers.Select(x => x.Name).ToListAsync();
Console.WriteLine("Thread After Async/Await: " + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
Output:
------ Test started: Assembly: EFTest.dll ------
Thread Before Async/Await: 6
Thread After Async/Await: 33
1 passed, 0 failed, 0 skipped, took 4.38 seconds (NUnit 3.10.1).
Again, the code after the await is executed on another thread from the original.
If you want to ensure that the code calling async code remains on the same thread then you need to use the Result on the Task to block the thread until the async task completes:
[Test]
public void TestAsync3()
{
using (var context = new TestDbContext())
{
Console.WriteLine("Thread Before Async: " + Thread.CurrentThread.ManagedThreadId.ToString());
var names = context.Customers.Select(x => x.Name).ToListAsync();
Console.WriteLine("Thread After Async: " + Thread.CurrentThread.ManagedThreadId.ToString());
var result = names.Result;
Console.WriteLine("Thread After Result: " + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
Output:
------ Test started: Assembly: EFTest.dll ------
Thread Before Async: 20
Thread After Async: 20
Thread After Result: 20
1 passed, 0 failed, 0 skipped, took 4.16 seconds (NUnit 3.10.1).
So as far as Unity, EF, etc. goes, you should be cautious about using async liberally where these classes are not thread safe. For instance the following code may lead to unexpected behaviour:
using (var context = new TestDbContext())
{
var ids = await context.Customers.Select(x => x.CustomerId).ToListAsync();
foreach (var id in ids)
{
var orders = await context.Orders.Where(x => x.CustomerId == id).ToListAsync();
// do stuff with orders.
}
}
As far as the code goes this looks fine, but DbContext is not thread-safe and the single DbContext reference will be running on a different thread when it is queried for Orders based on the await on the initial Customer load.
Use async when there is a significant benefit to it over synchronous calls, and you're sure the continuation will access only thread-safe code.
I'm amazed there is no mention of ConfigureAwait in this thread. I was looking for a confirmation that Unity did async/await the same way that it is done for "regular" C# and from what I see above it seems to be the case.
The thing is, by default, an awaited task will resume in the same threading context after completion. If you await on the main thread, it will resume on the main thread. If you await on a thread from the ThreadPool, it will use any available thread from the thread pool. You can always create different contexts for different purposes, like a DB access context and whatnot.
This is where ConfigureAwait is interesting. If you chain a call to ConfigureAwait(false) after your await, you are telling the runtime that you do not need to resume in the same context and therefore it will resume on a thread from the ThreadPool. Omitting a call to ConfigureAwait plays it safe and will resume in the same context (main thread, DB thread, ThreadPool context, whatever the context the caller was on).
So, starting on the main thread, you can await and resume in the main thread like so:
// Main thread
await WhateverTaskAync();
// Main thread
or go to the thread pool like so:
// Main thread
await WhateverTaskAsync().ConfigureAwait(false);
// Pool thread
Likewise, starting from a thread in the pool:
// Pool thread
await WhateverTaskAsync();
// Pool thread
is equivalent to :
// Pool thread
await WhateverTaskAsync().ConfigureAwait(false);
// Pool thread
To go back to the main thread, you would use an API that transfers to the main thread:
// Pool thread
await WhateverTaskAsync().ConfigureAwait(false);
// Pool thread
RunOnMainThread(()
{
// Main thread
NextStep()
});
// Pool thread, possibly before NextStep() is run unless RunOnMainThread is synchronous (which it normally isn't)
This is why people say that calling Task.Run runs code on a pool thread. The await is superfluous though...
// Main Thread
await Task.Run(()
{
// Pool thread
WhateverTaskAsync()
// Pool thread before WhateverTaskAsync completes because it is not awaited
});
// Main Thread before WhateverTaskAsync completes because it is not awaited
Now, calling ConfigureAwait(false) does not guarantee that the code inside the Async method is called in a separate thread. It only states that when returning from the await, you have no guarantee of being in the same threading context.
If your Async method looks like this:
private async Task WhateverTaskAsync()
{
int blahblah = 0;
for(int i = 0; i < 100000000; ++i)
{
blahblah += i;
}
}
... because there is actually no await inside the Async method, you will get a compilation warning and it will all run within the calling context. Depending on its ConfigureAwait state, it might resume on the same or a different context. If you want the method to run on a pool thread, you would instead write the Async method as such:
private Task WhateverTaskAsync()
{
return Task.Run(()
{
int blahblah = 0;
for(int i = 0; i < 100000000; ++i)
{
blahblah += i;
}
}
}
Hopefully, that clears up some things for others.

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.
});

TaskScheduler.Current and TaskScheduler.FromCurrentSynchronizationContext() difference?

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.

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