简体   繁体   中英

Encoding Task to run on a specific thread

I've updated an old project that was using SuperSocket to connect to old c++ servers. With the latest version (from 0.7.0 => 0.8.0.8) I got en exception when trying to reconnect (It says that the socket was opened on a different thread) I would like to have a class that enqueues tasks (First connection and reconnection) and runs them on a specific Thread.

I've seen this approach but when I try to run the task created as got an exception

ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.

Here's the class I've taken from the link above

public class SameThreadTaskScheduler : TaskScheduler, IDisposable
{
    #region publics
    public SameThreadTaskScheduler(string name)
    {
        scheduledTasks = new Queue<Task>();
        threadName = name;
    }
    public override int MaximumConcurrencyLevel => 1;

    public void Dispose()
    {
        lock (scheduledTasks)
        {
            quit = true;
            Monitor.PulseAll(scheduledTasks);
        }
    }
    #endregion

    #region protected overrides
    protected override IEnumerable<System.Threading.Tasks.Task> GetScheduledTasks()
    {
        lock (scheduledTasks)
        {
            return scheduledTasks.ToList();
        }
    }

    protected override void QueueTask(Task task)
    {
        if (myThread == null)
            myThread = StartThread(threadName);
        if (!myThread.IsAlive)
            throw new ObjectDisposedException("My thread is not alive, so this object has been disposed!");
        lock (scheduledTasks)
        {
            scheduledTasks.Enqueue(task);
            Monitor.PulseAll(scheduledTasks);
        }
    }

    public void Queue(Task task)
    {
        QueueTask(task);
    }

    protected override bool TryExecuteTaskInline(Task task, bool task_was_previously_queued)
    {
        return false;
    }

    #endregion

    private readonly Queue<System.Threading.Tasks.Task> scheduledTasks;
    private Thread myThread;
    private readonly string threadName;
    private bool quit;

    private Thread StartThread(string name)
    {
        var t = new Thread(MyThread) { Name = name };
        using (var start = new Barrier(2))
        {
            t.Start(start);
            ReachBarrier(start);
        }
        return t;
    }

    private void MyThread(object o)
    {
        Task tsk;
        lock (scheduledTasks)
        {
            //When reaches the barrier, we know it holds the lock.
            //
            //So there is no Pulse call can trigger until
            //this thread starts to wait for signals.
            //
            //It is important not to call StartThread within a lock.
            //Otherwise, deadlock!
            ReachBarrier(o as Barrier);
            tsk = WaitAndDequeueTask();
        }
        for (;;)
        {
            if (tsk == null)
                break;
            TryExecuteTask(tsk);
            lock (scheduledTasks)
            {
                tsk = WaitAndDequeueTask();
            }
        }
    }

    private Task WaitAndDequeueTask()
    {
        while (!scheduledTasks.Any() && !quit)
            Monitor.Wait(scheduledTasks);
        return quit ? null : scheduledTasks.Dequeue();
    }

    private static void ReachBarrier(Barrier b)
    {
        if (b != null)
            b.SignalAndWait();
    }
}

Here's how I call the task

public void RegisterServers()
{
    sameThreadTaskScheduler.Queue(new Task(() =>
    {
        ...something
    }));

What am I doing wrong?

You must start a task to bind it to a TaskScheduler . In your code, you're manually queuing the task, doing it the other way around, so the task doesn't get bound to your task scheduler (or any at all), and TryExecuteTask will fail with that error. The description is rather cryptic, as any actual TaskScheduler is different from null .

For a task that you created without running, there are the overloads of Task.RunSynchronously and Task.Start that take a TaskScheduler .

For a task that starts running automatically, there are the overloads of TaskFactory.StartNew and TaskFactory<TResult>.StartNew that take a TaskScheduler .

For continuation tasks, there are the overloads of Task.ContinueWith , Task<TResult>.ContinueWith , TaskFactory.ContinueWhenAll and TaskFactory.ContinueWhenAny that take a TaskScheduler .

The overloads that don't take a task scheduler are equivalent to specifying TaskScheduler.Current . In the case of TaskFactory , this is true for the default Task.Factory or one created without a task scheduler at the time that the factory's method is called, otherwise the factory's task scheduler is used.


Contrast with the overloads of the newer Task.Run , which always use TaskScheduler.Default . According to most experienced folks, the thread-pool scheduler is desired more often as the default than TaskScheduler.Current which may be thread-bound, but it was too late to change the contract for the existing APIs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM