I am writing a WPF project, using C# 5 and async/await.
I'd like to, during development, add some code that will alert the developer any task takes longer than a certain period of time. This will ensure that the developer never accidentally does file/network IO on the UI thread, as well as any other long running computations that should be moved to another thread.
Is there somewhere to override the TaskScheduler
, to wrap each Task
with the following?
private Task WrapTask(Task task)
{
return Task.Run(async () =>
{
var stopwatch = new Stopwatch();
stopwatch.Start();
await task;
stopwatch.Stop();
if (stopwatch.Elapsed > TimeSpan.FromMilliseconds(5))
{
// TODO: Log
Debug.WriteLine("A task took longer than expected.");
}
});
}
This should be transparent to the user, and also should be used when in the context of an async
/await
method.
THIS DOESN'T WORK AT ALL, JUST TO ILLUSTRATE: Maybe wrapping a TaskScheduler
like this, and then someone replacing the current one?
public class TaskSchedulerTimer : TaskScheduler
{
private readonly TaskScheduler _taskScheduler;
private readonly MethodInfo _queueTaskMethod;
private readonly MethodInfo _tryExecuteTaskInlineMethod;
private readonly MethodInfo _getScheduledTasksMethod;
public TaskSchedulerTimer(TaskScheduler taskScheduler)
{
_taskScheduler = taskScheduler;
_queueTaskMethod = typeof(TaskScheduler).GetMethod("QueueTask");
_tryExecuteTaskInlineMethod = typeof(TaskScheduler).GetMethod("TryExecuteTaskInline");
_getScheduledTasksMethod = typeof(TaskScheduler).GetMethod("GetScheduledTasks");
}
protected override void QueueTask(Task task)
{
_queueTaskMethod.Invoke(_taskScheduler, new object[] { WrapTask(task) });
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return (bool)_tryExecuteTaskInlineMethod.Invoke(_taskScheduler, new object[] { WrapTask(task), taskWasPreviouslyQueued });
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return (IEnumerable<Task>)_getScheduledTasksMethod.Invoke(_taskScheduler, new object[] { });
}
private Task WrapTask(Task task)
{
return Task.Run(async () =>
{
var stopwatch = new Stopwatch();
stopwatch.Start();
await task;
stopwatch.Stop();
if (stopwatch.Elapsed > TimeSpan.FromMilliseconds(5))
{
// TODO: Log
Debug.WriteLine("A task took longer than expected.");
}
});
}
}
Maybe I need to go lower, to the SynchronizationContext
, and do something similar there?
UPDATE: It seems that the current TaskScheduler
used in WPF wraps around the Dispatcher
. There appears to be some hooks on there, so I am covered for my purposes. However, I'd still like to know if my original question has a good answer.
FYI, here is my code for the timing stuff, in WPF.
private readonly Stopwatch _currentOperation = new Stopwatch();
Dispatcher.Hooks.OperationStarted += HooksOnOperationStarted;
Dispatcher.Hooks.OperationCompleted += HooksOnOperationCompleted;
Dispatcher.Hooks.OperationAborted += HooksOnOperationAborted;
private void HooksOnOperationStarted(object sender, DispatcherHookEventArgs dispatcherHookEventArgs)
{
Debug.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId);
_currentOperation.Start();
}
private void HooksOnOperationCompleted(object sender, DispatcherHookEventArgs dispatcherHookEventArgs)
{
Debug.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId);
_currentOperation.Stop();
if (_currentOperation.Elapsed > TimeSpan.FromMilliseconds(5))
{
// TODO: Log
Debug.WriteLine("A task took longer than expected.");
}
_currentOperation.Reset();
}
private void HooksOnOperationAborted(object sender, DispatcherHookEventArgs dispatcherHookEventArgs)
{
Debug.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId == Dispatcher.Thread.ManagedThreadId);
_currentOperation.Stop();
if (_currentOperation.Elapsed > TimeSpan.FromMilliseconds(5))
{
// TODO: Log
Debug.WriteLine("A task took longer than expected.");
}
_currentOperation.Reset();
}