SFu*_*n28 7 .net .net-4.0 .net-3.5 task-parallel-library
伙计们,
假设我使用来自线程10,11,12的CallContext.SetData()来存储三个新的对象Car实例.这些线程完成执行.然后我执行另一个使用线程10,11,12的多线程操作(可能与第一个不同的操作).GetData()将检索我存储的相同三个对象吗?或者现在上下文有些不同,那些对象消失了?
我的特定用例是任务并行库.我正在使用TPL来并行化一些操作,我想了解在TPL调用之间通过CallContext.SetData()存储的数据会发生什么.
编辑
Per @wageoghe建议我尝试了ThreadLocal并且它有效!
更新代码以证明它:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TlsTest
{
public class Program
{
public static void Main()
{
Console.WriteLine( "-------using threadpool---------" );
UseThreadPool();
Console.WriteLine( "-------using tasks---------" );
UseTasks();
Console.WriteLine( "-------using parallel for---------" );
UseParallelFor();
Console.ReadKey();
}
public static void UseThreadPool()
{
var finish = new CountdownEvent( TotalThreads );
for ( int i = 0 ; i < TotalThreads ; i++ )
{
ThreadPool.QueueUserWorkItem( x =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep( SleepMilliseconds );
if ( ThreadId.IsValueCreated )
{
Console.WriteLine( "thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value );
}
else
{
Console.WriteLine( "thread [{0}] - no Tls value" , id );
ThreadId.Value = id;
}
Thread.Sleep( SleepMilliseconds );
finish.Signal();
} );
}
finish.Wait();
}
public static void UseTasks()
{
const TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
var allTasks = new Task[ TotalThreads ];
for ( int i = 0 ; i < TotalThreads ; i++ )
{
Task task = Task.Factory.StartNew( () =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep( SleepMilliseconds );
if ( ThreadId.IsValueCreated )
{
Console.WriteLine( "thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value );
}
else
{
Console.WriteLine( "thread [{0}] - no Tls value" , id );
ThreadId.Value = id;
}
Thread.Sleep( SleepMilliseconds );
} , taskCreationOpt );
allTasks[ i ] = task;
}
Task.WaitAll( allTasks );
}
public static void UseParallelFor()
{
var options = new ParallelOptions();
options.MaxDegreeOfParallelism = 8;
Parallel.For( 0 , TotalThreads , options , i =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep( SleepMilliseconds );
if ( ThreadId.IsValueCreated )
{
Console.WriteLine( "thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value );
}
else
{
Console.WriteLine( "thread [{0}] - no Tls value" , id );
ThreadId.Value = id;
}
Thread.Sleep( SleepMilliseconds );
} );
}
private static readonly ThreadLocal<int> ThreadId = new ThreadLocal<int>();
private const int TotalThreads = 100;
private const int SleepMilliseconds = 500;
}
}
Run Code Online (Sandbox Code Playgroud)
[UPDATE]
实际上,我的原始答案(在这篇文章的底部)似乎有些错误!
我编写了一个小测试程序,用于测试从线程和Tasks,ThreadPool线程以及Parallel.For中的线程在CallContext中存储数据的场景.在Tasks测试和ThreadPool测试中,当重用相同的线程(由ManagedThreadId确定)时,不会再次看到存储在CallContext中的数据.但是,在Parallel.For的情况下,当重用相同的线程(由ManagedThreadId确定)时,再次看到存储在CallContext WAS中的数据.我觉得非常有趣.我不确定这些结果是否是预期的,或者我的程序是否有问题.
要尝试每种情况,只需取消注释所需的测试功能即可.
您将看到Tasks和ThreadPool线程在线程的后续重用中从未遇到CallContext数据而Parallel.For线程DO遇到CallContext数据.
Parallel.For的行为似乎不一致.当我运行Parallel.For的情况下,我可以看到,当重用该线程时,给定的线程不一定总能找到CallContext数据.例如,这是程序的一次运行的输出(使用UseParallelFor取消注释):
thread [9] - no CallContext value
thread [10] - no CallContext value
thread [11] - no CallContext value
thread [12] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext <-- this is expected as this is the main thread
thread [10] - no CallContext value
thread [13] - no CallContext value
thread [11] - no CallContext value
thread [12] - no CallContext value
thread [14] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [11], cc.thread [11] - value already in CallContext
thread [13] - no CallContext value
thread [15] - no CallContext value
thread [12], cc.thread [12] - value already in CallContext
thread [16] - no CallContext value
thread [14] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext
thread [10] - no CallContext value
thread [17] - no CallContext value
thread [13], cc.thread [13] - value already in CallContext
thread [15] - no CallContext value
thread [11] - no CallContext value
thread [12] - no CallContext value
thread [14], cc.thread [14] - value already in CallContext
thread [18] - no CallContext value
thread [16] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [13] - no CallContext value
thread [15], cc.thread [15] - value already in CallContext
thread [11], cc.thread [11] - value already in CallContext
thread [17] - no CallContext value
thread [19] - no CallContext value
thread [18] - no CallContext value
thread [16], cc.thread [16] - value already in CallContext
thread [14] - no CallContext value
thread [20] - no CallContext value
thread [12], cc.thread [12] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [21] - no CallContext value
thread [15] - no CallContext value
thread [11], cc.thread [11] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [13], cc.thread [13] - value already in CallContext
thread [19] - no CallContext value
thread [22] - no CallContext value
thread [18], cc.thread [18] - value already in CallContext
thread [16] - no CallContext value
thread [20] - no CallContext value
thread [14], cc.thread [14] - value already in CallContext
thread [12], cc.thread [12] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [23] - no CallContext value
thread [15], cc.thread [15] - value already in CallContext
thread [21] - no CallContext value
thread [11], cc.thread [11] - value already in CallContext
thread [17] - no CallContext value
thread [13], cc.thread [13] - value already in CallContext
thread [19], cc.thread [19] - value already in CallContext
thread [22] - no CallContext value
thread [16], cc.thread [16] - value already in CallContext
thread [18] - no CallContext value
thread [24] - no CallContext value
thread [20], cc.thread [20] - value already in CallContext
thread [14], cc.thread [14] - value already in CallContext
thread [12], cc.thread [12] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10] - no CallContext value
thread [15], cc.thread [15] - value already in CallContext
thread [21], cc.thread [21] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [13], cc.thread [13] - value already in CallContext
thread [22], cc.thread [22] - value already in CallContext
thread [18], cc.thread [18] - value already in CallContext
thread [16], cc.thread [16] - value already in CallContext
thread [14], cc.thread [14] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [15], cc.thread [15] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [18], cc.thread [18] - value already in CallContext
thread [16], cc.thread [16] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [18], cc.thread [18] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,并非如果在重用中找到一个值,它永远保留在CallContext中.在某些情况下,对于给定线程的几次迭代,在CallContext中找不到该值,因此将其添加.然后迭代将报告找到该值.然后,也许,下一次迭代会说没有找到该值.
结果告诉我,您不应该依赖于CallContext中保持完整的数据,以便在线程的重用之间清除给定的线程.他们还告诉我,在Parallel.For的情况下,你不应该依赖于在同一个线程的重用之间清理掉CallContext.
这是我的测试程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Remoting.Messaging;
namespace CallContextTest
{
class Program
{
static void Main(string[] args)
{
//UseTasks();
//UseThreadPool();
UseParallelFor();
Console.ReadKey();
}
public static void UseThreadPool()
{
int totalThreads = 100;
CountdownEvent finish = new CountdownEvent(totalThreads);
for (int i = 0; i < totalThreads; i++)
{
int ii = i;
ThreadPool.QueueUserWorkItem(x =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
object o = CallContext.GetData("threadid");
if (o == null)
{
//Always gets here.
Console.WriteLine("thread [{0}] - no CallContext value", id);
CallContext.SetData("threadid", id);
}
else
{
//Never gets here.
Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id);
}
Thread.Sleep(1000);
finish.Signal();
});
}
finish.Wait();
}
public static void UseTasks()
{
int totalThreads = 100;
TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
Task task = null;
Task[] allTasks = new Task[totalThreads];
for (int i = 0; i < totalThreads; i++)
{
int ii = i;
task = Task.Factory.StartNew(() =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
object o = CallContext.GetData("threadid");
if (o == null)
{
//Always gets here.
Console.WriteLine("thread [{0}] - no CallContext value", id);
CallContext.SetData("threadid", id);
}
else
{
//Never gets here.
Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id);
}
Thread.Sleep(1000);
}, taskCreationOpt);
allTasks[i] = task;
}
Task.WaitAll(allTasks);
}
public static void UseParallelFor()
{
int totalThreads = 100;
Parallel.For(0, totalThreads, i =>
{
int ii = i;
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
object o = CallContext.GetData("threadid");
if (o == null)
{
//Sometimes gets here.
Console.WriteLine("thread [{0}] - no CallContext value", id);
CallContext.SetData("threadid", id);
}
else
{
//Sometimes gets here as threads are reused.
Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id);
}
Thread.Sleep(1000);
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,根据上述测试程序和我的新评论,在这个答案的顶部,我原来的讨论似乎是错误的.从我的测试来看,在Task和ThreadPool线程的情况下,似乎存在于CallContext中的数据在后续重用相同线程id时不可用.但是,看起来存储在CallContext中的数据在Parallel.For的情况下可以重用相同的线程.
结束更新后忽略所有内容.
[结束更新]
我当然不是TPL专家,但我最近一直在关注CallContext.SetData(和LogicalSetData),所以我对CallContext的工作原理有所了解.这里有更好的人来描述在TPL的"上下文"中CallContext数据可能会发生什么或不会发生什么.
我对CallContext.SetData如何工作的理解是,当线程消失时,"数据"将被清除.因此,如果您创建一个新线程,并且在该线程上执行时,使用CallContext.SetData存储一些数据,那么当线程死亡时数据将消失.如果你使用的是ThreadPool线程,那么线程永远不会消亡(好吧,也许永远不会太强),所以通过CallContext.SetData存储的数据在下次有一些代码在(重用)线程中执行时仍然存在.
我的理解是任务并行库在内部使用ThreadPool,因此当再次使用底层ThreadPool线程时,通过CallContext.SetData存储的任何数据可能仍然存在.
编写一个或多个小测试应该很容易,看看当你将数据放入CallContext时会发生什么,然后检查它是否在同一个线程的后续使用中存在.
| 归档时间: |
|
| 查看次数: |
2326 次 |
| 最近记录: |