[英]Is it possible to determine parallelism of tasks by using Task.Delay to force a new thread?
[英]Is it possible always to force a new thread with Task?
每次调用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();
}
编辑:新代码如果你注释第一个for
语句就没有问题。 但是,如果您拥有它,哇,消息“线程重用”将写入控制台。 你能解释一下吗,因为我真的很困惑。
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();
}
如果在启动任务时指定TaskCreationOptions.LongRunning
提供调度程序的提示 ,默认调度程序将此提示作为为任务创建新线程的指示符。
这只是一个提示 - 我不确定我会依赖它......但我没有看到任何使用默认调度程序的反例。
添加Jon Skeet的答案,如果您想保证每次都创建一个新线程,您可以编写自己的TaskScheduler
来创建一个新线程。
您好,谢谢大家的答案。 你们都得到了+1。 所有建议的解决方案都不适合我的情况。 问题是,当你睡眠一个线程时,它会在某个时间点被重用。 以上人士建议:
我不喜欢它,但它有效。 基本上我阻止了线程,因此无法重用。 贝娄是扩展方法和工作实例。 再次谢谢你。
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);
}
}
}
只需使用新的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();
}
任务应该由调度程序管理。 Tasks的整个想法是运行时将决定何时需要新线程。 另一方面,如果你确实需要不同的线程,那么代码中的其他东西很可能就像Thread.Sleep()或线程本地存储的过度依赖一样。
正如所指出的,您可以创建自己的TaskScheduler并使用任务来创建线程,但是为什么要使用Tasks来开始呢?
试试这个:
var taskCompletionSource = new TaskCompletionSource<bool>();
Thread t = new Thread(() =>
{
try
{
Operation();
taskCompletionSource.TrySetResult(true);
}
catch (Exception e)
{
taskCompletionSource.TrySetException(e);
}
});
void Operation()
{
// Some work in thread
}
t.Start();
await taskCompletionSource.Task;
您还可以为Action,Func等编写扩展方法。 例如:
public static Task RunInThread(
this Action action,
Action<Thread> initThreadAction = null)
{
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
Thread thread = new Thread(() =>
{
try
{
action();
taskCompletionSource.TrySetResult(true);
}
catch (Exception e)
{
taskCompletionSource.TrySetException(e);
}
});
initThreadAction?.Invoke(thread);
thread.Start();
return taskCompletionSource.Task;
}
要么
public static Task<TResult> RunInThread<T1, T2, TResult>(
this Func<T1, T2, TResult> function,
T1 param1,
T2 param2,
Action<Thread> initThreadAction = null)
{
TaskCompletionSource<TResult> taskCompletionSource = new TaskCompletionSource<TResult>();
Thread thread = new Thread(() =>
{
try
{
TResult result = function(param1, param2);
taskCompletionSource.TrySetResult(result);
}
catch (Exception e)
{
taskCompletionSource.TrySetException(e);
}
});
initThreadAction?.Invoke(thread);
thread.Start();
return taskCompletionSource.Task;
}
并使用它:
var result = await some_function.RunInThread(param1, param2).ConfigureAwait(true);
这是一个自定义TaskScheduler
,它在每个任务的专用线程上执行任务:
public class ThreadPerTask_TaskScheduler : TaskScheduler
{
protected override void QueueTask(Task task)
{
var thread = new Thread(() => TryExecuteTask(task));
thread.IsBackground = true;
thread.Start();
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return TryExecuteTask(task);
}
protected override IEnumerable<Task> GetScheduledTasks() { yield break; }
}
用法示例:
var parallelOptions = new ParallelOptions()
{
MaxDegreeOfParallelism = 3,
TaskScheduler = new ThreadPerTask_TaskScheduler()
};
Parallel.ForEach(Enumerable.Range(1, 10), parallelOptions, item =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}]" +
$" Processing #{item}" +
(Thread.CurrentThread.IsBackground ? ", Background" : "") +
(Thread.CurrentThread.IsThreadPoolThread ? ", ThreadPool" : ""));
Thread.Sleep(1000); // Simulate CPU-bound work
});
输出:
20:38:56.770 [4] Processing #3, Background
20:38:56.770 [5] Processing #2, Background
20:38:56.770 [1] Processing #1
20:38:57.782 [1] Processing #4
20:38:57.783 [8] Processing #5, Background
20:38:57.783 [7] Processing #6, Background
20:38:58.783 [1] Processing #7
20:38:58.783 [10] Processing #8, Background
20:38:58.787 [9] Processing #9, Background
20:38:59.783 [1] Processing #10
这个自定义TaskScheduler
允许当前线程参与计算。 这在上面的示例中通过线程[1]
处理项目#1
、 #4
、 #7
和#10
进行了演示。 如果您不想发生这种情况,只需将TryExecuteTaskInline
的代码替换为return false;
.
另一个示例,以Task.Factory.StartNew
方法为特色。 在 100 个不同的线程上启动 100 个任务:
var oneThreadPerTask = new ThreadPerTask_TaskScheduler();
Task[] tasks = Enumerable.Range(1, 100).Select(_ =>
{
return Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // Simulate long-running work
}, default, TaskCreationOptions.None, oneThreadPerTask);
}).ToArray();
在这种情况下,当前线程不参与工作,因为所有任务都是通过调用它们的Start
方法而不是RunSynchronously
在幕后Start
的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.