Можно ли всегда заставлять новый поток с помощью задачи?

Я пытаюсь создать новый поток каждый раз, когда вызывается Task.Factory.StartNew. Вопрос заключается в том, как выполнить код ниже, не выбрасывая исключение:

static void Main(string[] args)
        {
            int firstThreadId = 0;

            Task.Factory.StartNew(() => firstThreadId = Thread.CurrentThread.ManagedThreadId);

            for (int i = 0; i < 100; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    while (true)
                    {
                        Thread.Sleep(1000);
                        if (firstThreadId == Thread.CurrentThread.ManagedThreadId)
                            throw new Exception("The first thread is reused.");
                    }
                });
            }
            Console.Read();
        }

EDIT: новый код, если вы прокомментируете первый оператор for, проблем нет. Но если у вас есть это, WOW, на консоль записывается сообщение "Повторное использование темы". Можете ли вы объяснить это, потому что я действительно запутался.

static void Main(string[] args)
        {
            ConcurrentDictionary<int, int> startedThreads = new ConcurrentDictionary<int, int>();

            for (int i = 0; i < 10; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    Task.Factory.StartNew(() =>
                    {
                        startedThreads.AddOrUpdate(Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId, (a, b) => b);
                    }, TaskCreationOptions.LongRunning);

                    for (int j = 0; j < 100; j++)
                    {
                        Task.Factory.StartNew(() =>
                        {
                            while (true)
                            {
                                Thread.Sleep(10);
                                if (startedThreads.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                                    Console.WriteLine("Thread reused");
                            }
                        }, TaskCreationOptions.LongRunning);
                    }
                });
            }

            Console.Read();
        }

Ответ 1

Привет, спасибо всем за ответы. У вас всех было +1. Все предлагаемое решение не помогло мне. Проблема в том, что когда вы спите поток, он будет повторно использован в некоторый момент времени. Выше люди предложили:

  • Использование LongRunning = > Это не сработает, если у вас есть вложенный/дочерний Задачи
  • custom task scheduler = > Я попытался написать свой собственный, а также попробовал это ThreadPerTaskScheduler, которые также не работают.
  • с использованием чистых потоков = > Все еще сбой...
  • вы также можете проверить этот проект на Multithreading.Scheduler github

Мое решение

Мне это не нравится, но оно работает. В основном я блокирую поток, чтобы его нельзя было повторно использовать. Ниже приведены методы расширения и рабочий пример. Еще раз спасибо.

https://gist.github.com/4150635

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    public static class ThreadExtensions
    {
        /// <summary>
        /// Blocks the current thread for a period of time so that the thread cannot be reused by the threadpool.
        /// </summary>
        public static void Block(this Thread thread, int millisecondsTimeout)
        {
            new WakeSleepClass(millisecondsTimeout).SleepThread();
        }

        /// <summary>
        /// Blocks the current thread so that the thread cannot be reused by the threadpool.
        /// </summary>
        public static void Block(this Thread thread)
        {
            new WakeSleepClass().SleepThread();
        }

        /// <summary>
        /// Blocks the current thread for a period of time so that the thread cannot be reused by the threadpool.
        /// </summary>
        public static void Block(this Thread thread, TimeSpan timeout)
        {
            new WakeSleepClass(timeout).SleepThread();
        }

        class WakeSleepClass
        {
            bool locked = true;
            readonly TimerDisposer timerDisposer = new TimerDisposer();

            public WakeSleepClass(int sleepTime)
            {
                var timer = new Timer(WakeThread, timerDisposer, sleepTime, sleepTime);
                timerDisposer.InternalTimer = timer;
            }

            public WakeSleepClass(TimeSpan sleepTime)
            {
                var timer = new Timer(WakeThread, timerDisposer, sleepTime, sleepTime);
                timerDisposer.InternalTimer = timer;
            }

            public WakeSleepClass()
            {
                var timer = new Timer(WakeThread, timerDisposer, Timeout.Infinite, Timeout.Infinite);
                timerDisposer.InternalTimer = timer;
            }

            public void SleepThread()
            {
                while (locked)
                    lock (timerDisposer) Monitor.Wait(timerDisposer);
                locked = true;
            }

            public void WakeThread(object key)
            {
                locked = false;
                lock (key) Monitor.Pulse(key);
                ((TimerDisposer)key).InternalTimer.Dispose();
            }

            class TimerDisposer
            {
                public Timer InternalTimer { get; set; }
            }
        }
    }

    class Program
    {
        private static readonly Queue<CancellationTokenSource> tokenSourceQueue = new Queue<CancellationTokenSource>();
        static void Main(string[] args)
        {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            tokenSourceQueue.Enqueue(tokenSource);

            ConcurrentDictionary<int, int> startedThreads = new ConcurrentDictionary<int, int>();
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(1000);
                Task.Factory.StartNew(() =>
                {
                    startedThreads.AddOrUpdate(Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId, (a, b) => b);
                    for (int j = 0; j < 50; j++)
                        Task.Factory.StartNew(() => startedThreads.AddOrUpdate(Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId, (a, b) => b));

                    for (int j = 0; j < 50; j++)
                    {
                        Task.Factory.StartNew(() =>
                        {
                            while (!tokenSource.Token.IsCancellationRequested)
                            {
                                if (startedThreads.ContainsKey(Thread.CurrentThread.ManagedThreadId)) Console.WriteLine("Thread reused");
                                Thread.CurrentThread.Block(10);
                                if (startedThreads.ContainsKey(Thread.CurrentThread.ManagedThreadId)) Console.WriteLine("Thread reused");
                            }
                        }, tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)
                        .ContinueWith(task =>
                        {
                            WriteExceptions(task.Exception);
                            Console.WriteLine("-----------------------------");
                        }, TaskContinuationOptions.OnlyOnFaulted);
                    }
                    Thread.CurrentThread.Block();
                }, tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)
                .ContinueWith(task =>
                {
                    WriteExceptions(task.Exception);
                    Console.WriteLine("-----------------------------");
                }, TaskContinuationOptions.OnlyOnFaulted);
            }

            Console.Read();
        }

        private static void WriteExceptions(Exception ex)
        {
            Console.WriteLine(ex.Message);
            if (ex.InnerException != null)
                WriteExceptions(ex.InnerException);
        }
    }
}

Ответ 2

Если вы указываете TaskCreationOptions.LongRunning при запуске задачи, это дает подсказку планировщику, который планировщик по умолчанию принимает в качестве индикатора для создания нового потока для задача.

Это только подсказка - я не уверен, что буду полагаться на нее... но я не видел никаких контрпримеров, использующих планировщик по умолчанию.

Ответ 3

Добавляя к Jon Skeet ответ, если вы хотите гарантировать, что новый поток создается каждый раз, вы можете написать свой собственный TaskScheduler, который создаст новый поток.

Ответ 4

Просто запустите потоки с новым Thread(), а затем Start() их

static void Main(string[] args)
{
    ConcurrentDictionary<int, int> startedThreads = new ConcurrentDictionary<int, int>();

    for (int i = 0; i < 10; i++)
    {
        new Thread(() =>
        {
            new Thread(() =>
            {
                startedThreads.AddOrUpdate(Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId, (a, b) => b);
            }).Start();

            for (int j = 0; j < 100; j++)
            {
                new Thread(() =>
                {
                    while (true)
                    {
                        Thread.Sleep(10);
                        if (startedThreads.ContainsKey(Thread.CurrentThread.ManagedThreadId))
                            Console.WriteLine("Thread reused");
                    }
                }).Start();
            }
        }).Start();
    }

    Console.Read();

}

Задачи должны управляться планировщиком. Вся идея задач заключается в том, что среда выполнения решит, когда нужен новый поток. С другой стороны, если вам нужны разные потоки, скорее всего, что-то еще в коде неверно, как чрезмерная зависимость от Thread.Sleep() или локального хранилища потоков.

Как уже указывалось, вы можете создать свой собственный TaskScheduler и использовать задачи для создания потоков, но затем зачем использовать Tasks для начала?