1

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();
}
Paul Knopf
  • 9,568
  • 23
  • 77
  • 142
  • http://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout – Hackerman Dec 09 '16 at 20:39
  • 1
    @Hackerman The question isn't about cancellation, merely timing the operation and logging the time, if relevant. – Servy Dec 09 '16 at 20:40
  • @Servy, maybe I miss read the title of the question, because it looks like the OP was trying to achieve some kind of timeout...I should have read the question as a whole...my mistake :) – Hackerman Dec 09 '16 at 20:45
  • 1
    `TaskScheduler isn't going to be the way to go (at least not by itself). A task scheduler is there just for executing tasks that inherently represent CPU bound work, which won't be all tasks. Unless you only care about CPU bound work offloaded to another thread; is that what you care about? – Servy Dec 09 '16 at 20:47
  • How will this ensure that the developer never accidentally does file/network IO on the UI thread? – Yacoub Massad Dec 09 '16 at 20:48
  • I really want to prevent long running operations, which would automatically including file/network IO. I am not trying to detect those specifically. – Paul Knopf Dec 09 '16 at 20:58

0 Answers0