Я пытаюсь создать новый поток каждый раз, когда вызывается 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 для начала?