I want to unit test a method that I have that performs and async operation:
Task.Factory.StartNew(() =>
{
// method to test and return value
var result = LongRunningOperation();
});
I stub the necessary methods etc in my unit test (written in c#) but the problem is that the async operation is not finished before I assert the test.
How can I get around this? Should I create a mock of the TaskFactory or any other tips to unit testing an async operation?
You'd have to have some way of faking out the task creation.
If you moved the Task.Factory.StartNew call to some dependency (ILongRunningOperationStarter) then you could create an alternative implementation which used TaskCompletionSource to create tasks which complete exactly where you want them to.
It can get a bit hairy, but it can be done. I blogged about this a while ago - unit testing a method which received tasks to start with, which of course made things easier. It's in the context of async/await in C# 5, but the same principles apply.
If you don't want to fake out the whole of the task creation, you could replace the task factory, and control the timing that way - but I suspect that would be even hairier, to be honest.
I would propose to stub a TaskScheduler in your method with a special implementation for unit tests. You need to prepare your code to use an injected TaskScheduler:
private TaskScheduler taskScheduler;
public void OperationAsync()
{
Task.Factory.StartNew(
LongRunningOperation,
new CancellationToken(),
TaskCreationOptions.None,
taskScheduler);
}
In your unit test you can use the DeterministicTaskScheduler described in this blog post to run the new task on the current thread. Your 'async' operation will be finished before you hit your first assert statement:
[Test]
public void ShouldExecuteLongRunningOperation()
{
// Arrange: Inject task scheduler into class under test.
DeterministicTaskScheduler taskScheduler = new DeterministicTaskScheduler();
MyClass mc = new MyClass(taskScheduler);
// Act: Let async operation create new task
mc.OperationAsync();
// Act: Execute task on the current thread.
taskScheduler.RunTasksUntilIdle();
// Assert
...
}
Try something like this...
object result = null;
Task t = Task.Factory.StartNew(() => result = LongRunningThing());
Task.Factory.ContinueWhenAll(new Task[] { t }, () =>
{
Debug.Assert(result != null);
});
Set UI and background task schedulars and replace them in unit test with this one.
Below code was copied from internet, sorry for missing reference to author:
public class CurrentThreadTaskScheduler : TaskScheduler
{
protected override void QueueTask(Task task)
{
TryExecuteTask(task);
}
protected override bool TryExecuteTaskInline(
Task task,
bool taskWasPreviouslyQueued)
{
return TryExecuteTask(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
public override int MaximumConcurrencyLevel => 1;
}
So to test code:
public TaskScheduler TaskScheduler
{
get { return taskScheduler ?? (taskScheduler = TaskScheduler.Current); }
set { taskScheduler = value; }
}
public TaskScheduler TaskSchedulerUI
{
get { return taskSchedulerUI ?? (taskSchedulerUI = TaskScheduler.FromCurrentSynchronizationContext()); }
set { taskSchedulerUI = value; }
}
public Task Update()
{
IsBusy = true;
return Task.Factory.StartNew( () =>
{
LongRunningTask( );
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler )
.ContinueWith( t => IsBusy = false, TaskSchedulerUI );
}
You will write following unit test:
[Test]
public void WhenUpdateThenAttributeManagerUpdateShouldBeCalled()
{
taskScheduler = new CurrentThreadTaskScheduler();
viewModel.TaskScheduler = taskScheduler;
viewModel.TaskSchedulerUI = taskScheduler;
viewModel.Update();
dataManagerMock.Verify( s => s.UpdateData( It.IsAny<DataItem>>() ) );
}
Related
In program, we have:
var task1 = some task;
task1.start()
If mock the task result in unit test, the result is returned by mock mechanism immediately before calling the start(), and raise "Start may not be called on a task that has completed" exception.
How to address that issue and compose a valid unit test?
Paste a simplified sample code here for clarity, which produces the above exception:
namespace TestTaskStart
{
public class TestMethods : ITestMethods
{
public async Task<int> AlwaysReturnOne(int number)
{
return await Task.FromResult(1);
}
}
}
namespace TestTaskStart {
public class TestInvoker
{
private ITestMethods testMethods;
public TestInvoker(ITestMethods testMethods)
{
this.testMethods = testMethods;
}
public async Task<int> GetANumberWrapperTask(int number)
{
// just an exmple of one tasks to be called
var task = this.testMethods.AlwaysReturnOne(number);
task.Start();
Task.WaitAll(task);
return task.Result;
}
}
}
namespace TestTaskStart {
[TestClass]
public class UnitTests
{
ITestMethods numberGetter;
TestInvoker testInvoker;
[TestInitialize]
public void Setup()
{
this.numberGetter = Substitute.For<ITestMethods>();
this.testInvoker = new TestInvoker(this.numberGetter);
}
[TestMethod]
public void TestGetANumberWrapper()
{
this.MockAlwaysReturnOneResult();
var result = testInvoker.GetANumberWrapperTask(5).Result;
}
private void MockAlwaysReturnOneResult()
{
this.numberGetter.AlwaysReturnOne(Arg.Any<int>()).Returns(1);
}
}
}
The Task.Start method can only be called on "cold" tasks, in other words on tasks that have not been started yet. Such tasks can only be created with the Task constructor. Tasks created by invoking asynchronous methods implemented with the async keyword are "hot", in other words they are already started upon creation. From the docs:
Exceptions
InvalidOperationException
The Task is not in a valid state to be started. It may have already been started, executed, or canceled, or it may have been created in a manner that doesn't support direct scheduling.
This is also a good reading: A Tour of Task, Part 10: Promise Tasks
I'm trying to use the reactiveui test scheduler with an async method in a test.
The test hangs when the async call is awaited.
The root cause seems to be a command that's awaited in the async method.
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
await SomeAsyncMethod();
// *** execution never gets here
Debugger.Break();
});
private async Task SomeAsyncMethod()
{
var command = ReactiveCommand.CreateFromTask(async () =>
{
await Task.Delay(100);
});
// *** this hangs
await command.Execute();
}
How can I do an async call in combination with the test scheduler that does not deadlock?
I'm using reactiveui 9.4.1
EDIT:
I've tried the WithAsync() method as suggested in Funks answer, but the behaviour is the same.
How can I do an async call in combination with the test scheduler?
In short
command.Execute() is a cold observable. You need to subscribe to it, instead of using await.
Given your interest in TestScheduler, I take it you want to test something involving time. However, from the When should I care about scheduling section:
threads created via "new Thread()" or "Task.Run" can't be controlled in a unit test.
So, if you want to check, for example, if your Task completes within 100ms, you're going to have to wait until the async method completes. To be sure, that's not the kind of test TestScheduler is meant for.
The somewhat longer version
The purpose of TestScheduler is to verify workflows by putting things in motion and verifying state at certain points in time. As we can only manipulate time on a TestScheduler, you'd typically prefer not to wait on real async code to complete, given there's no way to fast forward actual computations or I/O. Remember, it's about verifying workflows: vm.A has new value at 20ms, so vm.B should have new val at 120ms,...
So how can you test the SUT?
1\ You could mock the async method using scheduler.CreateColdObservable
public class ViewModelTests
{
[Fact]
public void Test()
{
string observed = "";
new TestScheduler().With(scheduler =>
{
var observable = scheduler.CreateColdObservable(
scheduler.OnNextAt(100, "Done"));
observable.Subscribe(value => observed = value);
Assert.Equal("", observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", observed);
scheduler.AdvanceByMs(1);
Assert.Equal("Done", observed);
});
}
}
Here we basically replaced command.Execute() with var observable created on scheduler.
It's clear the example above is rather simple, but with several observables notifying each other this kind of test can provide valuable insights, as well as a safety net while refactoring.
Ref:
Answer by Paul Betts
Control Time with the TestScheduler
2\ You could reference the IScheduler explicitly
a) Using the schedulers provided by RxApp
public class MyViewModel : ReactiveObject
{
public string Observed { get; set; }
public MyViewModel()
{
Observed = "";
this.MyCommand = ReactiveCommand
.CreateFromTask(SomeAsyncMethod);
}
public ReactiveCommand<Unit, Unit> MyCommand { get; }
private async Task SomeAsyncMethod()
{
await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
Observed = "Done";
}
}
public class ViewModelTests
{
[Fact]
public void Test()
{
new TestScheduler().With(scheduler =>
{
var vm = new MyViewModel();
vm.MyCommand.Execute().Subscribe();
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(1);
Assert.Equal("Done", vm.Observed);
});
}
}
Note
CreateFromTask creates a ReactiveCommand with asynchronous execution logic. There's no need to define the Test method as async or await the TestScheduler.
Within the With extension method's scope RxApp.TaskpoolScheduler = RxApp.MainThreadScheduler = the new TestScheduler().
b) Managing your own schedulers through constructor injection
public class MyViewModel : ReactiveObject
{
private readonly IScheduler _taskpoolScheduler;
public string Observed { get; set; }
public MyViewModel(IScheduler scheduler)
{
_taskpoolScheduler = scheduler;
Observed = "";
this.MyCommand = ReactiveCommand
.CreateFromTask(SomeAsyncMethod);
}
public ReactiveCommand<Unit, Unit> MyCommand { get; }
private async Task SomeAsyncMethod()
{
await _taskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
Observed = "Done";
}
}
public class ViewModelTests
{
[Fact]
public void Test()
{
new TestScheduler().With(scheduler =>
{
var vm = new MyViewModel(scheduler); ;
vm.MyCommand.Execute().Subscribe();
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(0);
Assert.Equal("Done", vm.Observed);
});
}
}
Ref:
Kent Boogaert's Answer
Testing Rx code - ISchedulerProvider
Let's close ranks with another quote from Haacked:
Unfortunately, and this next point is important, the TestScheduler doesn’t extend into real life, so your shenanigans are limited to your asynchronous Reactive code. Thus, if you call Thread.Sleep(1000) in your test, that thread will really be blocked for a second. But as far as the test scheduler is concerned, no time has passed.
Have you tried to use ConfigureAwait(false) when calling nested method?
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
// this hangs
await SomeAsyncMethod().ConfigureAwait(false);
// ***** execution will never get to here
Debugger.Break();
}
Please try using .ConfigureAwait(false) on all your async methods.
This will provide you non-blocking behavior.
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
await SomeAsyncMethod().ConfigureAwait(false);
// *** execution never gets here
Debugger.Break();
}).ConfigureAwait(false);
private async Task SomeAsyncMethod()
{
var command = ReactiveCommand.CreateFromTask(async () =>
{
await Task.Delay(100).ConfigureAwait(false);
}).ConfigureAwait(false);
// *** this hangs
await command.Execute();
}
Another way to test whether the problem is related with ConfigureAwait is to port your project to Asp.Net Core and test it there.
Asp.net core does not need to use ConfigureAwait to prevent this blocking issue.
Check this for Reference
I have a "High-Precision" timer class that I need to be able to be start, stop & pause / resume. To do this, I'm tying together a couple of different examples I found on the internet, but I'm not sure if I'm using Tasks with asnyc / await correctly.
Here is my relevant code:
//based on http://haukcode.wordpress.com/2013/01/29/high-precision-timer-in-netc/
public class HighPrecisionTimer : IDisposable
{
Task _task;
CancellationTokenSource _cancelSource;
//based on http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx
PauseTokenSource _pauseSource;
Stopwatch _watch;
Stopwatch Watch { get { return _watch ?? (_watch = Stopwatch.StartNew()); } }
public bool IsPaused
{
get { return _pauseSource != null && _pauseSource.IsPaused; }
private set
{
if (value)
{
_pauseSource = new PauseTokenSource();
}
else
{
_pauseSource.IsPaused = false;
}
}
}
public bool IsRunning { get { return !IsPaused && _task != null && _task.Status == TaskStatus.Running; } }
public void Start()
{
if (IsPaused)
{
IsPaused = false;
}
else if (!IsRunning)
{
_cancelSource = new CancellationTokenSource();
_task = new Task(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning);
_task.Start();
}
}
public void Stop()
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}
public void Pause()
{
if (!IsPaused)
{
if (_watch != null)
{
_watch.Stop();
}
}
IsPaused = !IsPaused;
}
async void ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
// DO CUSTOM TIMER STUFF...
}
if (_watch != null)
{
_watch.Stop();
_watch = null;
}
_cancelSource = null;
_pauseSource = null;
}
public void Dispose()
{
if (IsRunning)
{
_cancelSource.Cancel();
}
}
}
Can anyone please take a look and provide me some pointers on whether I'm doing this correctly?
UPDATE
I have tried modifying my code per Noseratio's comments below, but I still cannot figure out the syntax. Every attempt to pass the ExecuteAsync() method to either TaskFactory.StartNew or Task.Run, results in a compilation error like the following:
"The call is ambiguous between the following methods or properties: TaskFactory.StartNew(Action, CancellationToken...) and TaskFactory.StartNew<Task>(Func<Task>, CancellationToken...)".
Finally, is there a way to specify the LongRunning TaskCreationOption without having to provide a TaskScheduler?
async **Task** ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
//...
}
}
public void Start()
{
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, null);
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token);
//_task = Task.Run(ExecuteAsync, _cancelSource.Token);
}
UPDATE 2
I think I've narrowed this down, but still not sure about the correct syntax. Would this be the right way to create the task so that the consumer / calling code continues on, with the task spinning-up and starting on a new asynchronous thread?
_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);
//**OR**
_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Here are some points:
async void methods are only good for asynchronous event handlers (more info). Your async void ExecuteAsync() returns instantly (as soon as the code flow reaches await _pauseSource inside it). Essentially, your _task is in the completed state after that, while the rest of ExecuteAsync will be executed unobserved (because it's void). It may even not continue executing at all, depending on when your main thread (and thus, the process) terminates.
Given that, you should make it async Task ExecuteAsync(), and use Task.Run or Task.Factory.StartNew instead of new Task to start it. Because you want your task's action method be async, you'd be dealing with nested tasks here, i.e. Task<Task>, which Task.Run would automatically unwrap for you. More info can be found here and here.
PauseTokenSource takes the following approach (by design, AFAIU): the consumer side of the code (the one which calls Pause) actually only requests a pause, but doesn't synchronize on it. It will continue executing after Pause, even though the producer side may not have reached the awaiting state yet, i.e. await _pauseSource.Token.WaitWhilePausedAsync(). This may be ok for your app logic, but you should be aware of it. More info here.
[UPDATE] Below is the correct syntax for using Factory.StartNew. Note Task<Task> and task.Unwrap. Also note _task.Wait() in Stop, it's there to make sure the task has completed when Stop returns (in a way similar to Thread.Join). Also, TaskScheduler.Default is used to instruct Factory.StartNew to use the thread pool scheduler. This is important if your create your HighPrecisionTimer object from inside another task, which in turn was created on a thread with non-default synchronization context, e.g. a UI thread (more info here and here).
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class HighPrecisionTimer
{
Task _task;
CancellationTokenSource _cancelSource;
public void Start()
{
_cancelSource = new CancellationTokenSource();
Task<Task> task = Task.Factory.StartNew(
function: ExecuteAsync,
cancellationToken: _cancelSource.Token,
creationOptions: TaskCreationOptions.LongRunning,
scheduler: TaskScheduler.Default);
_task = task.Unwrap();
}
public void Stop()
{
_cancelSource.Cancel(); // request the cancellation
_task.Wait(); // wait for the task to complete
}
async Task ExecuteAsync()
{
Console.WriteLine("Enter ExecuteAsync");
while (!_cancelSource.IsCancellationRequested)
{
await Task.Delay(42); // for testing
// DO CUSTOM TIMER STUFF...
}
Console.WriteLine("Exit ExecuteAsync");
}
}
class Program
{
public static void Main()
{
var highPrecisionTimer = new HighPrecisionTimer();
Console.WriteLine("Start timer");
highPrecisionTimer.Start();
Thread.Sleep(2000);
Console.WriteLine("Stop timer");
highPrecisionTimer.Stop();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
}
}
I'm adding code for running long running task (infinite with cancelation) with internal sub tasks:
Task StartLoop(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(async () => {
while (true)
{
if (cancellationToken.IsCancellationRequested)
break;
await _taskRunner.Handle(cancellationToken);
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
}
},
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
I have this class I want to unit test:
public class SomeClass
{
public void Foo()
{
Bar();
}
private void Bar()
{
Task.Factory.StartNew(() =>
{
// Do something that takes some time (e.g. an HTTP request)
});
}
}
And this is how my unit test looks like:
[TestMethod]
public void TestFoo()
{
// Arrange
var obj = new SomeClass();
// Act
obj.Foo();
obj.Foo();
obj.Foo();
// Assert
/* I need something to wait on all tasks to finish */
Assert.IsTrue(...);
}
So, I need to make the unit test thread wait until all tasks started in the Bar method have finished their job before starting my assertions.
IMPORTANT: I cannot change SomeClass
How can I do that?
One way to solve this problem is to define your own task scheduler in such a way that would allow you to keep track of the completion of your nested tasks. For example, you could define a scheduler that executes tasks synchronously, as below:
class SynchronousTaskScheduler : TaskScheduler
{
protected override void QueueTask(Task task)
{
this.TryExecuteTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool wasPreviouslyQueued)
{
return this.TryExecuteTask(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
yield break;
}
}
Subsequently, create an instance of this synchronous task scheduler, and use it to execute a root task which, in turn, spawns all of your "hidden" tasks. Since nested tasks inherit the current task scheduler from their parent, all your inner tasks will also get run on our synchronous scheduler, implying that our outermost StartNew call will only return when all tasks complete.
TaskScheduler scheduler = new SynchronousTaskScheduler();
Task.Factory.StartNew(() =>
{
// Arrange
var obj = new SomeClass();
// Act
obj.Foo();
obj.Foo();
obj.Foo();
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler);
// Assert
/* I need something to wait on all tasks to finish */
Assert.IsTrue(...);
A downside to this approach is that you will lose all concurrency from your tasks; however, you could fix this by enhancing the custom scheduler to one which is concurrent but still allows you to track executing tasks.
Task.WaitAll(the, list, of, task, objects, you, need, to, wait, on);
If it's a void async method then you can't do it. The design is broken. They're only for fire and forget.
Not sure if you are allowed to make this change but I got it to work doing this:
namespace ParallelProgramming.Playground
{
public class SomeClass
{
public Task Foo()
{
return Bar();
}
private static Task Bar()
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("I fired off. Thread ID: {0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000);
return true; //or whatever else you want.
});
}
}
[TestClass]
public class StackOverflow
{
[TestMethod]
public void TestFoo()
{
// Arrange
var obj = new SomeClass();
var results = new ConcurrentBag<Task>();
var waitForMe = Task.Factory.StartNew(() =>
{
// Act
results.Add(obj.Foo());
results.Add(obj.Foo());
results.Add(obj.Foo());
return true;
});
Task.WaitAll(waitForMe);
// Assert
/* I need something to wait on all tasks to finish */
Assert.IsTrue(waitForMe.Result);
Assert.AreEqual(3, results.Count);
}
}
}
I have some methods returning Task<T> on which I can await at will. I'd like to have those Tasks executed on a custom TaskScheduler instead of the default one.
var task = GetTaskAsync ();
await task;
I know I can create a new TaskFactory (new CustomScheduler ()) and do a StartNew () from it, but StartNew () takes an action and create the Task, and I already have the Task (returned behind the scenes by a TaskCompletionSource)
How can I specify my own TaskScheduler for await ?
I think what you really want is to do a Task.Run, but with a custom scheduler. StartNew doesn't work intuitively with asynchronous methods; Stephen Toub has a great blog post about the differences between Task.Run and TaskFactory.StartNew.
So, to create your own custom Run, you can do something like this:
private static readonly TaskFactory myTaskFactory = new TaskFactory(
CancellationToken.None, TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None, new MyTaskScheduler());
private static Task RunOnMyScheduler(Func<Task> func)
{
return myTaskFactory.StartNew(func).Unwrap();
}
private static Task<T> RunOnMyScheduler<T>(Func<Task<T>> func)
{
return myTaskFactory.StartNew(func).Unwrap();
}
private static Task RunOnMyScheduler(Action func)
{
return myTaskFactory.StartNew(func);
}
private static Task<T> RunOnMyScheduler<T>(Func<T> func)
{
return myTaskFactory.StartNew(func);
}
Then you can execute synchronous or asynchronous methods on your custom scheduler.
The TaskCompletionSource<T>.Task is constructed without any action and the scheduler
is assigned on the first call to ContinueWith(...) (from Asynchronous Programming with the Reactive Framework and the Task Parallel Library — Part 3).
Thankfully you can customize the await behavior slightly by implementing your own class deriving from INotifyCompletion and then using it in a pattern similar to await SomeTask.ConfigureAwait(false) to configure the scheduler that the task should start using in the OnCompleted(Action continuation) method (from await anything;).
Here is the usage:
TaskCompletionSource<object> source = new TaskCompletionSource<object>();
public async Task Foo() {
// Force await to schedule the task on the supplied scheduler
await SomeAsyncTask().ConfigureScheduler(scheduler);
}
public Task SomeAsyncTask() { return source.Task; }
Here is a simple implementation of ConfigureScheduler using a Task extension method with the important part in OnCompleted:
public static class TaskExtension {
public static CustomTaskAwaitable ConfigureScheduler(this Task task, TaskScheduler scheduler) {
return new CustomTaskAwaitable(task, scheduler);
}
}
public struct CustomTaskAwaitable {
CustomTaskAwaiter awaitable;
public CustomTaskAwaitable(Task task, TaskScheduler scheduler) {
awaitable = new CustomTaskAwaiter(task, scheduler);
}
public CustomTaskAwaiter GetAwaiter() { return awaitable; }
public struct CustomTaskAwaiter : INotifyCompletion {
Task task;
TaskScheduler scheduler;
public CustomTaskAwaiter(Task task, TaskScheduler scheduler) {
this.task = task;
this.scheduler = scheduler;
}
public void OnCompleted(Action continuation) {
// ContinueWith sets the scheduler to use for the continuation action
task.ContinueWith(x => continuation(), scheduler);
}
public bool IsCompleted { get { return task.IsCompleted; } }
public void GetResult() { }
}
}
Here's a working sample that will compile as a console application:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Example {
class Program {
static TaskCompletionSource<object> source = new TaskCompletionSource<object>();
static TaskScheduler scheduler = new CustomTaskScheduler();
static void Main(string[] args) {
Console.WriteLine("Main Started");
var task = Foo();
Console.WriteLine("Main Continue ");
// Continue Foo() using CustomTaskScheduler
source.SetResult(null);
Console.WriteLine("Main Finished");
}
public static async Task Foo() {
Console.WriteLine("Foo Started");
// Force await to schedule the task on the supplied scheduler
await SomeAsyncTask().ConfigureScheduler(scheduler);
Console.WriteLine("Foo Finished");
}
public static Task SomeAsyncTask() { return source.Task; }
}
public struct CustomTaskAwaitable {
CustomTaskAwaiter awaitable;
public CustomTaskAwaitable(Task task, TaskScheduler scheduler) {
awaitable = new CustomTaskAwaiter(task, scheduler);
}
public CustomTaskAwaiter GetAwaiter() { return awaitable; }
public struct CustomTaskAwaiter : INotifyCompletion {
Task task;
TaskScheduler scheduler;
public CustomTaskAwaiter(Task task, TaskScheduler scheduler) {
this.task = task;
this.scheduler = scheduler;
}
public void OnCompleted(Action continuation) {
// ContinueWith sets the scheduler to use for the continuation action
task.ContinueWith(x => continuation(), scheduler);
}
public bool IsCompleted { get { return task.IsCompleted; } }
public void GetResult() { }
}
}
public static class TaskExtension {
public static CustomTaskAwaitable ConfigureScheduler(this Task task, TaskScheduler scheduler) {
return new CustomTaskAwaitable(task, scheduler);
}
}
public class CustomTaskScheduler : TaskScheduler {
protected override IEnumerable<Task> GetScheduledTasks() { yield break; }
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; }
protected override void QueueTask(Task task) {
TryExecuteTask(task);
}
}
}
There is no way to embed rich async features into a custom TaskScheduler. This class was not designed with async/await in mind. The standard way to use a custom TaskScheduler is as an argument to the Task.Factory.StartNew method. This method does not understand async delegates. It is possible to provide an async delegate, but it is treated as any other delegate that returns some result. To get the actual awaited result of the async delegate one must call Unwrap() to the task returned.
This is not the problem though. The problem is that the TaskScheduler infrastructure does not treat the async delegate as a single unit of work. Each task is split into multiple mini-tasks (using every await as a separator), and each mini-task is processed individually. This severely restricts the asynchronous functionality that can be implemented on top of this class. As an example here is a custom TaskScheduler that is intended to queue the supplied tasks one at a time (to limit the concurrency in other words):
public class MyTaskScheduler : TaskScheduler
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
protected async override void QueueTask(Task task)
{
await _semaphore.WaitAsync();
try
{
await Task.Run(() => base.TryExecuteTask(task));
await task;
}
finally
{
_semaphore.Release();
}
}
protected override bool TryExecuteTaskInline(Task task,
bool taskWasPreviouslyQueued) => false;
protected override IEnumerable<Task> GetScheduledTasks() { yield break; }
}
The SemaphoreSlim should ensure that only one Task would run at a time. Unfortunately it doesn't work. The semaphore is released prematurely, because the Task passed in the call QueueTask(task) is not the task that represents the whole work of the async delegate, but only the part until the first await. The other parts are passed to the TryExecuteTaskInline method. There is no way to correlate these task-parts, because no identifier or other mechanism is provided. Here is what happens in practice:
var taskScheduler = new MyTaskScheduler();
var tasks = Enumerable.Range(1, 5).Select(n => Task.Factory.StartNew(async () =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Item {n} Started");
await Task.Delay(1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Item {n} Finished");
}, default, TaskCreationOptions.None, taskScheduler))
.Select(t => t.Unwrap())
.ToArray();
Task.WaitAll(tasks);
Output:
05:29:58.346 Item 1 Started
05:29:58.358 Item 2 Started
05:29:58.358 Item 3 Started
05:29:58.358 Item 4 Started
05:29:58.358 Item 5 Started
05:29:59.358 Item 1 Finished
05:29:59.374 Item 5 Finished
05:29:59.374 Item 4 Finished
05:29:59.374 Item 2 Finished
05:29:59.374 Item 3 Finished
Disaster, all tasks are queued at once.
Conclusion: Customizing the TaskScheduler class is not the way to go when advanced async features are required.
Update: Here is another observation, regarding custom TaskSchedulers in the presence of an ambient SynchronizationContext. The await mechanism by default captures the current SynchronizationContext, or the current TaskScheduler, and invokes the continuation on either the captured context
or the scheduler. If both are present, the current SynchronizationContext is preferred, and the current TaskScheduler is ignored. Below is a demonstration of this behavior, in a WinForms application¹:
private async void Button1_Click(object sender, EventArgs e)
{
await Task.Factory.StartNew(async () =>
{
MessageBox.Show($"{Thread.CurrentThread.ManagedThreadId}, {TaskScheduler.Current}");
await Task.Delay(1000);
MessageBox.Show($"{Thread.CurrentThread.ManagedThreadId}, {TaskScheduler.Current}");
}, default, TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
}
Clicking the button causes two messages to popup sequentially, with this information:
1, System.Threading.Tasks.SynchronizationContextTaskScheduler
1, System.Threading.Tasks.ThreadPoolTaskScheduler
This experiment shows that only the first part of the asynchronous delegate, the part before the first await, was scheduled on the non-default scheduler.
This behavior limits even further the practical usefulness of custom TaskSchedulers in an async/await-enabled environment.
¹ Windows Forms applications have a WindowsFormsSynchronizationContext installed automatically, when the Application.Run method is called.
Can you fit for this method call:
await Task.Factory.StartNew(
() => { /* to do what you need */ },
CancellationToken.None, /* you can change as you need */
TaskCreationOptions.None, /* you can change as you need */
customScheduler);
After the comments it looks like you want to control the scheduler on which the code after the await is run.
The compile creates a continuation from the await that runs on the current SynchronizationContext by default. So your best shot is to set up the SynchronizationContext before calling await.
There are some ways to await a specific context. See Configure Await from Jon Skeet, especially the part about SwitchTo, for more information on how to implement something like this.
EDIT:
The SwitchTo method from TaskEx has been removed, as it was too easy to misuse. See the MSDN Forum for reasons.
Faced with same issue, tried to use LimitedConcurrencyLevelTaskScheduler, but it does not support async tasks. So...
Just wrote my own small simple Scheduler, that allow to run async Tasks based on global ThreadPool (and Task.Run method) with ability to limit current max degree of parallelism. It is enough for my exact purposes, maybe will also help you, guys.
Main demo code (console app, dotnet core 3.1) :
static async Task Main(string[] args)
{
//5 tasks to run per time
int concurrentLimit = 5;
var scheduler = new ThreadPoolConcurrentScheduler(concurrentLimit);
//catch all errors in separate event handler
scheduler.OnError += Scheduler_OnError;
// just monitor "live" state and output to console
RunTaskStateMonitor(scheduler);
// simulate adding new tasks "on the fly"
SimulateAddingTasksInParallel(scheduler);
Console.WriteLine("start adding 50 tasks");
//add 50 tasks
for (var i = 1; i <= 50; i++)
{
scheduler.StartNew(myAsyncTask);
}
Console.WriteLine("50 tasks added to scheduler");
Thread.Sleep(1000000);
}
Supporting code (place it in the same place) :
private static void Scheduler_OnError(Exception ex)
{
Console.WriteLine(ex.ToString());
}
private static int currentTaskFinished = 0;
//your sample of async task
static async Task myAsyncTask()
{
Console.WriteLine("task started ");
using (HttpClient httpClient = new HttpClient())
{
//just make http request to ... wikipedia!
//sorry, Jimmy Wales! assume,guys, you will not DDOS wiki :)
var uri = new Uri("https://wikipedia.org/");
var response = await httpClient.GetAsync(uri);
string result = await response.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(result))
Console.WriteLine("error, await is not working");
else
Console.WriteLine($"task result : site length is {result.Length}");
}
//or simulate it using by sync sleep
//Thread.Sleep(1000);
//and for tesing exception :
//throw new Exception("my custom error");
Console.WriteLine("task finished ");
//just incrementing total ran tasks to output in console
Interlocked.Increment(ref currentTaskFinished);
}
static void SimulateAddingTasksInParallel(ThreadPoolConcurrentScheduler taskScheduler)
{
int runCount = 0;
Task.Factory.StartNew(() =>
{
while (true)
{
runCount++;
if (runCount > 5)
break;
//every 10 sec 5 times
Thread.Sleep(10000);
//adding new 5 tasks from outer task
Console.WriteLine("start adding new 5 tasks!");
for (var i = 1; i <= 5; i++)
{
taskScheduler.StartNew(myAsyncTask);
}
Console.WriteLine("new 5 tasks added!");
}
}, TaskCreationOptions.LongRunning);
}
static void RunTaskStateMonitor(ThreadPoolConcurrentScheduler taskScheduler)
{
int prev = -1;
int prevQueueSize = -1;
int prevFinished = -1;
Task.Factory.StartNew(() =>
{
while (true)
{
// getting current thread count in working state
var currCount = taskScheduler.GetCurrentWorkingThreadCount();
// getting inner queue state
var queueSize = taskScheduler.GetQueueTaskCount();
//just output overall state if something changed
if (prev != currCount || queueSize != prevQueueSize || prevFinished != currentTaskFinished)
{
Console.WriteLine($"Monitor : running tasks:{currCount}, queueLength:{queueSize}. total Finished tasks : " + currentTaskFinished);
prev = currCount;
prevQueueSize = queueSize;
prevFinished = currentTaskFinished;
}
// check it every 10 ms
Thread.Sleep(10);
}
}
, TaskCreationOptions.LongRunning);
}
Scheduler :
public class ThreadPoolConcurrentScheduler
{
private readonly int _limitParallelThreadsCount;
private int _threadInProgressCount = 0;
public delegate void onErrorDelegate(Exception ex);
public event onErrorDelegate OnError;
private ConcurrentQueue<Func<Task>> _taskQueue;
private readonly object _queueLocker = new object();
public ThreadPoolConcurrentScheduler(int limitParallelThreadsCount)
{
//set maximum parallel tasks to run
_limitParallelThreadsCount = limitParallelThreadsCount;
// thread-safe queue to store tasks
_taskQueue = new ConcurrentQueue<Func<Task>>();
}
//main method to start async task
public void StartNew(Func<Task> task)
{
lock (_queueLocker)
{
// checking limit
if (_threadInProgressCount >= _limitParallelThreadsCount)
{
//waiting new "free" threads in queue
_scheduleTask(task);
}
else
{
_startNewTask(task);
}
}
}
private void _startNewTask(Func<Task> task)
{
Interlocked.Increment(ref _threadInProgressCount);
Task.Run(async () =>
{
try
{
await task();
}
catch (Exception e)
{
//Console.WriteLine(e);
OnError?.Invoke(e);
}
}).ContinueWith(_onTaskEnded);
}
//will be called on task end
private void _onTaskEnded(Task task)
{
lock (_queueLocker)
{
Interlocked.Decrement(ref _threadInProgressCount);
//queue has more priority, so if thread is free - let's check queue first
if (!_taskQueue.IsEmpty)
{
if (_taskQueue.TryDequeue(out var result))
{
_startNewTask(result);
}
}
}
}
private void _scheduleTask(Func<Task> task)
{
_taskQueue.Enqueue(task);
}
//returning in progress task count
public int GetCurrentWorkingThreadCount()
{
return _threadInProgressCount;
}
//return number of tasks waiting to run
public int GetQueueTaskCount()
{
lock (_queueLocker) return _taskQueue.Count;
}
}
Few notes :
First - check comments to it, maybe it is the worst code ever!
Did not test in prod
Did not implement cancellation tokens and any other functionality, that should be there, but i'm too lazy. Sorry