多线程编程学习笔记(四)
同步
实现同步的3种方法:
1、Thread.Join()
2、WaitHandle
//使用自动事件
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
asyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),asyncOpIsDone);
asyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
//使用手工代码
ManualResetEvent masyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),masyncOpIsDone);
masyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
//masyncOpIsDone仍处于有信号状态
//必须手工复位
masyncOpIsDone.Reset(); //如果该代码注释掉,则主线程不会等待第2个子线程结束。
ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation),masyncOpIsDone);
masyncOpIsDone.WaitOne();//asyncOpIsDone自动被复位
3、Monitor
//下面实现同步的例子
using System;
using System.Threading;
namespace AppThreadDemo
{
public class Buffer
{
const int size = 1;
char[] buffer = new char[size];//临界资源
//n缓冲区的字符数
//head队列头
//tail队列尾
int n=0,head = 0, tail =0 ;
public void Put(char ch)
{
Console.WriteLine("Put 开始");
lock(this)
{
n++;//放一个字符
while(n> size){//如果n>size说明缓冲区满,故期待读线程取数据
Console.WriteLine("Put Monitor.Wait");
Monitor.Wait(this);
}
buffer[tail]=ch;
tail = (tail+1) % size ;
Console.WriteLine("Put tail={0}\tbuffer={1}\tn={2}",tail,buffer[tail],n);
if (n <=0) //如果缓冲区为空,则通知所有等待线程
{
Console.WriteLine("Put 通知等待的所有线程");
Monitor.PulseAll(this);
}
}
}
public char Get()
{
char ch;
Console.WriteLine("Get 开始");
lock(this)
{
n--;//先取一个字符
while(n<0)//如果缓冲区为空,则等待写线程写入数据
{
Console.WriteLine("Get Monitor.Wait");
Monitor.Wait(this);
}
ch = buffer[head];
head = (head + 1) % size;
Console.WriteLine("Get tail={0}\tbuffer={1}\tn={2}",tail,buffer[tail],n);
if(n>=size)//如果缓冲区满了,则通知所有等待线程
{
Console.WriteLine("Get 通知等待的所有线程");
Monitor.PulseAll(this);
}
return ch;
}
}
}
class App
{
static public void bufferRead()
{
Object o = AppDomain.CurrentDomain.GetData("Buffer");
if(o!=null)
{
Buffer buffer = (Buffer)o;
for(int i=0;i<8;i++)
{
Console.WriteLine("读线程\t{0}读到字符\t{1}",Thread.CurrentThread.GetHashCode(),buffer.Get());
}
}
Console.WriteLine("读取结束");
}
static public void bufferWrite()
{
Object o = AppDomain.CurrentDomain.GetData("Buffer");
char[] msg ={'A','B','C','D','E','F','G','H','I','J','1','2'};
if(o!=null)
{
Buffer buffer = (Buffer)o;
for(int i=0;i<msg.GetLength(0);i++)
{
Console.WriteLine("写线程\t{0}写字符\t{1}",Thread.CurrentThread.GetHashCode(),msg[i]);
buffer.Put(msg[i]);
}
}
Console.WriteLine("写结束");
}
static public void demoBuffer()
{
Buffer buffer = new Buffer();
AppDomain.CurrentDomain.SetData("Buffer",buffer);
Thread threadReader = new Thread(new ThreadStart(App.bufferRead));
Thread threadWriter = new Thread(new ThreadStart(App.bufferWrite));
threadReader.Start();
threadWriter.Start();
threadWriter.Join();
threadReader.Join();
}
static int Main(string[] args)
{
demoBuffer();
return 0;
}
}
}
3种方法的总结:
A、Thread.Join用以等待特定的线程实例thread结束,常用以主线程和子线程之间的同步。
B、AutoResetEvent和ManualResetEvent用事件信号量的方式实现多个线程之间的同步。
C、Monitor要和Lock/SyncLock语句配合才能实现同步。
《.net核心技术-原理与架构》
多线程编程学习笔记(五)
处理周期事件
1、System.WinForms.Timer
Timer的Tick事件代码:
Interlocked.Increment(ref _count);
2、ThreadPool
A.生成WaitOrTimerCallback事例
B.生成一个同步对象
C.添加到线程池
例1:
/*RegisterWaitForSingleObject
下面的示例演示了几种线程处理功能。
使用 RegisterWaitForSingleObject 将需要执行的任务以 ThreadPool 线程的方式排队。
使用 AutoResetEvent 发出信号,通知执行任务。
用 WaitOrTimerCallback 委托处理超时和信号。
用 RegisteredWaitHandle 取消排入队列的任务。
*/
using System;
using System.Threading;
// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
public RegisteredWaitHandle Handle = null;
public string OtherInfo = "default";
}
public class Example {
public static void Main(string[] args) {
// The main thread uses AutoResetEvent to signal the
// registered wait handle, which executes the callback
// method.
AutoResetEvent ev = new AutoResetEvent(false);
TaskInfo ti = new TaskInfo();
ti.OtherInfo = "First task";
// The TaskInfo for the task includes the registered wait
// handle returned by RegisterWaitForSingleObject. This
// allows the wait to be terminated when the object has
// been signaled once (see WaitProc).
ti.Handle = ThreadPool.RegisterWaitForSingleObject(
ev,
new WaitOrTimerCallback(WaitProc),
ti,
100,
false
);
// The main thread waits three seconds, to demonstrate the
// time-outs on the queued thread, and then signals.
Thread.Sleep(3100);
Console.WriteLine("Main thread signals.");
ev.Set();
// The main thread sleeps, which should give the callback
// method time to execute. If you comment out this line, the
// program usually ends before the ThreadPool thread can execute.
Thread.Sleep(1000);
// If you start a thread yourself, you can wait for it to end
// by calling Thread.Join. This option is not available with
// thread pool threads.
}
// The callback method executes when the registered wait times out,
// or when the WaitHandle (in this case AutoResetEvent) is signaled.
// WaitProc unregisters the WaitHandle the first time the event is
// signaled.
public static void WaitProc(object state, bool timedOut) {
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo) state;
string cause = "TIMED OUT";
if (!timedOut) {
cause = "SIGNALED";
// If the callback method executes because the WaitHandle is
// signaled, stop future execution of the callback method
// by unregistering the WaitHandle.
if (ti.Handle != null)
ti.Handle.Unregister(null);
}
Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
ti.OtherInfo,
Thread.CurrentThread.GetHashCode().ToString(),
cause
);
}
}
例2:
using System;
using System.Threading;
class State
{
private int nCalledTimes = 0;
public void Called()
{
Interlocked.Increment(ref nCalledTimes);
}
public int CalledTimes
{
get
{
return nCalledTimes;
}
}
}
class App
{
static public void PeriodicMethod(object state , bool timeOut)
{
// timeOut为false时,说明等待有效,否则超时
Console.WriteLine("\nThread {0}开始处理定时事件",Thread.CurrentThread.GetHashCode());
if(!timeOut)
Console.WriteLine("获得等待信号");
else
Console.WriteLine("超时事件发生");
if(state!=null)
{
((State)state).Called();
Console.WriteLine("调用了{0}次",((State)state).CalledTimes);
}
Thread.Sleep(100);
Console.WriteLine("Thread {0}处理定时事件完毕\n",Thread.CurrentThread.GetHashCode());
}
static public void Main()
{
AutoResetEvent myEvent = new AutoResetEvent(false);
WaitOrTimerCallback waitOrTimerCallback = new WaitOrTimerCallback(App.PeriodicMethod);
int timeout = 1000;
bool executeOnlyOnce = false;
State state = new State();
ThreadPool.RegisterWaitForSingleObject(myEvent , waitOrTimerCallback , state ,timeout,executeOnlyOnce);
//Thread.Sleep(10000);
myEvent.Set();
Console.WriteLine("按任意键退出");
Console.ReadLine();
}
}
3、System.Threading.Timer
A.实例化一个TimerCallback代理callback
B.创建一个System.Threading.Timer实例timer
C.如果有必要,调用 timer.Change重新设置timer的durTime和period
D.用timer.Dispose释放timer
using System;
using System.Threading;
class State
{
private int threadID = -1;
private AutoResetEvent firstTimerFired = null;
public State(int threadID , AutoResetEvent firstTimerFired)
{
this.threadID = threadID;
this.firstTimerFired = firstTimerFired;
}
public void Show()
{
Console.WriteLine("thread.HashCode={0}\tthreadID={1}在工作",Thread.CurrentThread.GetHashCode(),threadID);
}
public AutoResetEvent FirstTimerFired
{
get
{
return firstTimerFired;
}
set
{
firstTimerFired = value;
}
}
}
class App
{
public static void Main()
{
Console.WriteLine("每2秒执行一次时钟事件");
TimerCallback callback = new TimerCallback(App.CheckStatus);
Timer timer1 = new Timer(callback , null ,1000 ,2000);
AutoResetEvent firstTimerFired = new AutoResetEvent(false);
State state = new State(2,firstTimerFired);
Timer timer2 = new Timer(callback ,state , 5000 ,0);//定时器事件只触发一次,period为0
firstTimerFired.WaitOne();
Console.WriteLine("按回车继续...");
Console.ReadLine();
timer2.Change(2000,1000);
Console.WriteLine("按回车继续...");
Console.ReadLine();
timer1.Dispose();
timer2.Dispose();
}
static void CheckStatus(object state)
{
if (state !=null)
{
((State)state).Show();
if(((State)state).FirstTimerFired != null)
((State)state).FirstTimerFired.Set();
}
else
{
Console.WriteLine("tread.HashCode = {0}\tthreadID={1}在工作",Thread.CurrentThread.GetHashCode(),-1);
}
}
}
4、System.Timers.Timer
基于服务器的计时器的关键编程元素
Timer 组件引发一个名为 Timer.Elapsed 的事件。您可以为这个事件创建处理程序来执行处理要发生的一切。
Timer 组件的一些更重要的属性和方法还包含:
Interval 属性用来设置引发事件的时间范围,以毫秒计。例如,值为 1000 的时间间隔将一秒钟引发一次事件。
AutoReset 属性决定在给定时间间隔过去之后计时器是否继续引发事件。如果设置成 true,计时器继续重新计算时间间隔并引发事件。如果为 false,它在时间间隔过去后只引发一次事件,然后停止。
Start 方法将计时器的 Enabled 属性设置为 true,它允许计时器开始引发事件。如果计时器已经是启用状态,则调用 Start 方法将重置该计时器。
Stop 方法将计时器的 Enabled 属性设置成 false,以防止计时器再引发事件。
A.创建System.Timers.Timer对象kicker
B.设置周期
C.设置AutoReset为true
D.设置kicker的Elapsed事件
E.启动kicker
F.如果需要,可以重新设置kicker的Interval属性
G.停止记时器
using System;
using System.Timers;
using System.Threading;
class App
{
private static DateTime stopTime = new DateTime(2005,4,2);
static void ElapsedHandler(object sender , ElapsedEventArgs e)
{
if (DateTime.Compare(e.SignalTime , stopTime) > 0 )
{
Console.WriteLine("Thread {0} 处理定事事件",Thread.CurrentThread.GetHashCode());
Thread.Sleep(100);
}
}
static public void Main()
{
System.Timers.Timer kicker = new System.Timers.Timer();
kicker.Interval =1000;
kicker.AutoReset = true;
kicker.Elapsed += new ElapsedEventHandler(ElapsedHandler);
kicker.Start();
Thread.Sleep(2100);
Console.WriteLine("改变时间间隔");
kicker.Interval = 2000;
Thread.Sleep(2100);
Console.WriteLine("结束定事器");
//kicker.Stop();
stopTime = DateTime.Now;
Thread.Sleep(2100);
Console.WriteLine("重新启动定事器");
kicker.Start();
Thread.Sleep(8100);
Console.WriteLine("按任意键退出");
//Console.ReadLine();
//Thread.Sleep(14100);
kicker.Stop();
stopTime = DateTime.Now;
}
}
System.Winforms.Timer、System.Threading.Timer、System.Timers.Timer,通过设置定时周期、定时事件、可以启动、终止、再启动定时器、重新设置定时器属性等。功能依次增强。
ThreadPool一旦设置好时钟属性并启动后,就不能对定时器进行控制。
《.net核心技术-原理与架构》
多线程编程学习笔记(六)
线程局部存储(TLS)
存放局部存储步骤:
1、申请数据槽
LocalDataStoreSlot slot = Thread.GetNamedDataSlot("para");
如果不存在名为para的数据槽,将分配一个所有线程均可用的para数据槽
2、往数据槽存放数据
MyPara para = new MyPara();
para.I = i;
Thread.SetData(slot,para);
3、如有必要,释放数据槽
Thread.FreeNamedDataSlot("para");
释放数据槽要小心,该操作将使所有线程存放在被释放的数据槽中的数据丢失。
读取局部存储步骤:
1、根据名字子线程局部存储中获取特定的数据槽
LocalDataStoreSlot slot = Thread.GetNamedDataSlot("para");
2、从数据槽获取数据
Object o = Thread.GetData(slot);
if (o != null)
{
//转化为特定类型
MyPara para = (MyPara) o ;
//....
}
using System;
using System.Threading;
using System.Runtime.Remoting; //ObjectHandle所在的命名空间
namespace AppThreadDemo
{
class App
{
static private void ShowInfo()
{
Console.WriteLine("当前应用域的名字为:{0}",AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("当前线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
}
static private void demoThreadTLSAcrossThreadCallBack()
{
Console.WriteLine("\n");
App.ShowInfo();
Console.WriteLine("\n读取在另一个线程中创建的线程局部存储");
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.demoTLS();
obj.setTLS(200);
obj.demoTLS();
Console.WriteLine("\n");
}
//主线程跟子线程在同一个应用域中执行,子线程不能获取主线程的局部存储中para
//数据槽的数据;子线程对该线程的局部存储中para数据槽中数据的修改,没有改变
//主线程局部存储区中para数据槽中的数据
static private void demoThreadTLS()
{
App.ShowInfo();
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.setTLS(100);
Thread thread = new Thread(new ThreadStart(demoThreadTLSAcrossThreadCallBack));
thread.Start();
thread.Join();
obj.demoTLS();
}
//对于执行在不同应用域的线程数据槽的数据互不访问,即各自槽数据的修改不会影响对方的数据槽
//数据,他们也不能让在别的应用域上的线程访问
static private void demoThreadTLSAcrossAppDomain()
{
App.ShowInfo();
AppDomain child = AppDomain.CreateDomain("ChildDomain",null,null);
ObjectHandle oh = (ObjectHandle)child.CreateInstance("mydll","Demo.MyDemoObj");
Demo.MyDemoObj obj = (Demo.MyDemoObj)oh.Unwrap();
Console.WriteLine("\n在另外一个应用域中设置当前线程的局部存储,并获得");
obj.setTLS(100);
obj.demoTLS();
Console.WriteLine("\n在当前应用域中不能获得其他应用域中的当前线程的局部存储");
Demo.MyDemoObj obj2 = new Demo.MyDemoObj();
obj2.demoTLS();
Console.WriteLine("\n在当前应用域中设置当前线程的局部存储,并获得");
obj2.setTLS(200);
obj2.demoTLS();
Console.WriteLine("\n当前应用域中设置当前的线程的局部存储不会改变当前线程在另一个应用域中的局部存储");
obj.demoTLS();
}
//对于线程池中的工作线程,也可以有自己的线程局部存储.但因为工作线程是按需分配的,可能存在一个工作线程
//执行多个逻辑线程(逻辑线程即工作队列中的一项任务),即一个物理线程的局部存储可以对应了几个逻辑线程的局部存储.
//下面代码显示了:逻辑线程没有独立的线程局部存储,它的局部存储位于执行逻辑线程的物理线程的局部存储中,
//这样第2个逻辑线程执行时从物理线程的局部存储中获得para参数.要是逻辑线程位于不同的应用域则获取数据
//槽中的数据会失败,代码见demoLogicalThreadTLSAcrossDoMain方法.
static private void demoLogicalThreadTLSCallBack(Object state)
{
Console.WriteLine("\n");
App.ShowInfo();
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.demoTLS();
obj.setTLS(100);
obj.demoTLS();
((AutoResetEvent)state).Set();
}
static private void demoLogicalTreadTLS()
{
App.ShowInfo();
Console.WriteLine("\n执行第1个工作池逻辑线程");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("\n执行第2个工作池逻辑线程");
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("\n执行第3个工作池逻辑线程");
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
}
static private void demoLogicalThreadTLSCallBackAcrossDomain(Object state)
{
Console.WriteLine("\n");
AppDomain child = AppDomain.CreateDomain("ChildDomain",null,null);
ObjectHandle oh = (ObjectHandle) child.CreateInstance("mydll","Demo.MyDemoObj");
Demo.MyDemoObj obj = (Demo.MyDemoObj)oh.Unwrap();
obj.demoTLS();
obj.setTLS(500);
obj.demoTLS();
((AutoResetEvent)state).Set();
}
static private void demoLogicalThreadTLSAcrossDoMain()
{
App.ShowInfo();
Console.WriteLine("\n执行第1个工作池逻辑线程");
AutoResetEvent asyncOpIsDone = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBack),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("\n执行第2个工作池逻辑线程");
ThreadPool.QueueUserWorkItem(new WaitCallback(App.demoLogicalThreadTLSCallBackAcrossDomain),asyncOpIsDone);
asyncOpIsDone.WaitOne();
Console.WriteLine("执行结束");
}
static int Main(string[] args)
{
//demoThreadTLS();
//demoThreadTLSAcrossAppDomain();
//demoLogicalTreadTLS();
demoLogicalThreadTLSAcrossDoMain();
return 0;
}
}
}
局部线程的总结:
1.主线程跟子线程在同一个应用域中执行,子线程不能获取主线程的局部存储中para数据槽的数据;子线程对该线程的局部存储中para数据槽中数据的修改,没有改变主线程局部存储区中para数据槽中的数据
2.对于执行在不同应用域的线程数据槽的数据互不访问,即各自槽数据的修改不会影响对方的数据槽数据,他们也不能让在别的应用域上的线程访问
3.对于线程池中的工作线程,也可以有自己的线程局部存储.但因为工作线程是按需分配的,可能存在一个工作线程执行多个逻辑线程(逻辑线程即工作队列中的一项任务),即一个物理线程的局部存储可以对应了几个逻辑线程的局部存储.
多线程编程学习笔记(七) 选择自 sillywxj 的 Blog
线程静态成员(ThreadStatic)
特点:
1、不能被2个不同的线程共享
线程静态成员隶属于特定的线程。同一个静态成员针对不同的线程有不同的实例。
2、只在第1个生成其实例的线程中初始化线程静态成员的初始化
隶属于第1个线程的线程静态成员将根据类声明中指定的初始化函数生成特定的实例,而隶属于第1个以后的线程的静态成员将按照默认情况初始化。在不指定初始值的情况下,如果它是值类型,可依赖初始化为其默认值的字段,如果它是引用类型,则可依赖初始化为空引用(Visual Basic 中为 Nothing)的字段。
3、同一线程的线程静态成员在不同的应用域有不同的实例
线程静态成员跟特定的线程的特定应用域相关。
4、同一线程同一应用域中同类型的对象将共享一个线程静态成员
代码:
1、创建threadstaticdll.dll
using System;
namespace Demo
{
public class MyPara : MarshalByRefObject
{
public int I;
public MyPara()
{
I = 0;
}
public MyPara(int i)
{
I=i;
}
}
public class MyDemoObj : MarshalByRefObject
{
[ThreadStatic]
private static int threadStaticValue = 10;
[ThreadStatic]
private static MyPara para = new MyPara(100);
public MyDemoObj()
{
if (para == null)
para = new MyPara();
}
public void doIncrease()
{
threadStaticValue ++;
para.I ++;
}
public void ShowThreadStaticValue()
{
Console.WriteLine("threadStaticValue = {0}\tpara.I={1}",threadStaticValue,para.I);
}
}
}
2.控制台程序
using System;
using System.Threading;
using System.Runtime.Remoting;
namespace ThreadStaticDemo
{
class App
{
static private void ShowInfo()
{
Console.WriteLine("当前应用域的名字为:{0}",AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("当前线程的代码为:{0}",Thread.CurrentThread.GetHashCode().ToString());
}
static private void demoThreadStaticCallBack()
{
App.ShowInfo();
//因为子线程第2个创建Demo.MyDemoObj实例的线程,因此不会触发线程静态成员的初始化过程,
//而直接用默认值初始化
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.ShowThreadStaticValue();
obj.doIncrease();
obj.ShowThreadStaticValue();
Console.WriteLine("线程执行结束");
}
static private void demoThreadSatic()
{
Console.WriteLine("\n");
App.ShowInfo();
//主线程第1个创建Demo.MyDemoObj实例的线程,所以触发线程静态成员的初始化过程
Demo.MyDemoObj obj = new Demo.MyDemoObj();
obj.ShowThreadStaticValue();
//在主线程等待的过程中,子线程执行demoThreadStaticCallBack,首先生成一个Demo.MyDemoObj对象实例
Thread thread = new Thread(new ThreadStart(App.demoThreadStaticCallBack));
thread.Start();
thread.Join();
obj.ShowThreadStaticValue();
}
static private void demoThreadStaticAcrossAppDomain()
{
App.ShowInfo();
AppDomain child = AppDomain.CreateDomain("ChildDomain",null,null);
ObjectHandle oh = (ObjectHandle)child.CreateInstance("threadstaticdll","Demo.MyDemoObj");
Demo.MyDemoObj obj = (Demo.MyDemoObj) oh.Unwrap();
obj.doIncrease();
obj.doIncrease();
//执行完上面的2个doIncrease()后,threadStaticValue=12,para.I=102
Console.WriteLine("\n主线程在ChildDomain应用域的线程静态成员");
obj.ShowThreadStaticValue();
//因为主线程是第1个在当前应用域创建MyDemoObj对象的线程,所以将
//引发线程静态成员初始化的过程
Demo.MyDemoObj obj2=new Demo.MyDemoObj();
obj2.doIncrease();
obj2.doIncrease();
obj2.doIncrease();
Console.WriteLine("obj2:");
obj2.ShowThreadStaticValue();
//因为主线程在当前应用域创建线程静态成员,所以这时仅执行MyDemoObj
//的构造函数
Demo.MyDemoObj obj3 = new Demo.MyDemoObj();
Console.WriteLine("obj3:");
obj3.doIncrease();//这个值在obj2上加
Console.WriteLine("\n主线程在{0}应用域的静态成员",AppDomain.CurrentDomain.FriendlyName);
obj3.ShowThreadStaticValue();
}
static void Main()
{
//demoThreadSatic();
demoThreadStaticAcrossAppDomain();
}
}
}
《.net核心技术--原理与架构》
要是逻辑线程位于不同的应用域则获取数据槽中的数据会失败.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架