C# 关于Monitor

Anod 2012-07-11 09:12:53

public partial class Form1 : Form
{
private ThreadStart myStart;
private ParameterizedThreadStart myPStart;
int m, n, t;
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
myStart = new ThreadStart(ShowNamea);
Thread thA = new Thread(myStart);
myPStart = new ParameterizedThreadStart(ShowNameObj);
Thread thB = new Thread(myPStart);
thA.Start();
thB.Start(2);

}
private void ShowNamea()
{
while(true)
{
Thread.Sleep(100);
try
{
Monitor.Enter(t);
if (t % 2 != 0)
{
Monitor.Wait(t);
}
t += 2;
Monitor.Pulse(t);
}
finally
{
Monitor.Exit(t);
} m++;
}
}
private void ShowNameObj(object obj)
{
int num = (int)obj;
while (true)
{
Thread.Sleep(100);
try
{
Monitor.Enter(t);
if (t % 2 != num)
{
Monitor.Wait(t);
}
t += num;
Monitor.Pulse(t);
}
finally
{
Monitor.Exit(t);
}
n++;
}
}
}


运行会报“从不同步的代码块中调用了对象同步方法”的异常,求解惑。
...全文
347 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
ViewStates 2012-07-11
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 的回复:]

引用 1 楼 的回复:
只能Monitor引用类型,不能是值类型

C# code

public partial class Form1 : Form
{
private ThreadStart myStart;
private ParameterizedThreadStart myPStart;
int m, n, s =……
[/Quote]
你装箱拆箱后T已经不是之前的T了。。然后会导致在发送信号时已经不是LOCK时的线程了
Only the current owner of the lock can signal a waiting object using Pulse.

The thread that currently owns the lock on the specified object invokes this method to signal the next thread in line for the lock. Upon receiving the pulse, the waiting thread is moved to the ready queue. When the thread that invoked Pulse releases the lock, the next thread in the ready queue (which is not necessarily the thread that was pulsed) acquires the lock.
Anod 2012-07-11
  • 打赏
  • 举报
回复
如果不用Monitor锁定的对象传数据就没问题,用了就出错。代码修改容易,要么对Monitor专门创建一个引用类型来管理锁定代码段的访问,要么直接用lock(虽然Lock实际上还是Monitor),我需要的是理解为什么,MSDN上没看到对此有详细解释。求大神解释。
Anod 2012-07-11
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 的回复:]
只能Monitor引用类型,不能是值类型
[/Quote]

public partial class Form1 : Form
{
private ThreadStart myStart;
private ParameterizedThreadStart myPStart;
int m, n, s = 0;
object t;
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
t = s;
myStart = new ThreadStart(ShowNamea);
Thread thA = new Thread(myStart);
myPStart = new ParameterizedThreadStart(ShowNameObj);
Thread thB = new Thread(myPStart);
thA.Start();
thB.Start(2);

}
private void ShowNamea()
{
while(true)
{
Thread.Sleep(100);
try
{
Monitor.Enter(t);
if ((int)t % 2 != 0)
{
Monitor.Wait(t);
}
t =(int)t+ 2;
Monitor.Pulse(t);
}
finally
{
Monitor.Exit(t);
} m++;
}
}
private void ShowNameObj(object obj)
{
int num = (int)obj;
while (true)
{
Thread.Sleep(100);
try
{
Monitor.Enter(t);
if ((int)t % 2 != num)
{
Monitor.Wait(t);
}
t =(int)t + num;
Monitor.Pulse(t);
}
finally
{
Monitor.Exit(t);
}
n++;
}
}
}

刚才已经注意到了,但依旧不行啊。。。
SocketUpEx 2012-07-11
  • 打赏
  • 举报
回复
只能Monitor引用类型,不能是值类型
Anod 2012-07-11
  • 打赏
  • 举报
回复
老问题不是使用Monitor.Pulse的原因,我是在学习C#的多线程控制,不单是使用,而是尽量的深入理解。刚才C++转过来。

你看贴不认真哇。我上面在5L时已经找到了老问题的原因所在,并发现新问题求解,但是你在10L还没发现。。。。。

这时间我已经把新问题搞定了,原因是我对IL还没摸索明白,try块的范围指定在方法的最后放着的,之前翻的快没看到。
      .try IL_001d to IL_0049
finally handler IL_0049 to IL_0051
.try IL_000e to IL_0055
finally handler IL_0055 to IL_0058

通过这个还可以发现 Lock块本身是带有一个try块的,即在异常时保证它内部使用的Monitor.Exit可以执行。好了 这个帖子可以结贴了
lizhibin11 2012-07-11
  • 打赏
  • 举报
回复
虽然你改成了object,但你在获得锁之后,对t重新赋值,指向了另一个对象,退出时指向的就又是不同的对象了。
另外我不知道你为什么用Monitor.Pulse,它的用法看一下那篇文章。
Anod 2012-07-11
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 的回复:]
补充上面少复制了一段:lock的代码段退出是这样的

C# code
IL_0046: nop
IL_0047: leave.s IL_0051
IL_0049: ldloc.1
IL_004a: call void [mscorlib]System.Threading.Monitor::……
[/Quote]

晕了 还是没复制完全,竟然没看清楚是两个leave:
      IL_003c:  box        [mscorlib]System.Int32
IL_0041: stfld object WindowsApplication1.Form1::t
IL_0046: nop
IL_0047: leave.s IL_0051
IL_0049: ldloc.1
IL_004a: call void [mscorlib]System.Threading.Monitor::Exit(object)
IL_004f: nop
IL_0050: endfinally
IL_0051: nop
IL_0052: nop
IL_0053: leave.s IL_0058
IL_0055: nop
IL_0056: nop
IL_0057: endfinally
IL_0058: nop

还没明白为什么
Anod 2012-07-11
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 lizhibin11 的回复:]
int装箱导致,不同代码段锁的对象是不一样的,在同一个代码段中,进入和释放的对象也是不一样的。另外我看到还有Monitor.Pulse,它的用法最好看一下这篇经典文章http://topic.csdn.net/u/20111209/18/2fa51d2e-ba01-4c28-a7d2-2719e6de3c8f.html
[/Quote]

哈哈 问题已经找到了,新问题能否解决?
lizhibin11 2012-07-11
  • 打赏
  • 举报
回复
int装箱导致,不同代码段锁的对象是不一样的,在同一个代码段中,进入和释放的对象也是不一样的。另外我看到还有Monitor.Pulse,它的用法最好看一下这篇经典文章http://topic.csdn.net/u/20111209/18/2fa51d2e-ba01-4c28-a7d2-2719e6de3c8f.html
Anod 2012-07-11
  • 打赏
  • 举报
回复
补充上面少复制了一段:lock的代码段退出是这样的
      IL_0046:  nop        
IL_0047: leave.s IL_0051
IL_0049: ldloc.1
IL_004a: call void [mscorlib]System.Threading.Monitor::Exit(object)
IL_004f: nop
IL_0050: endfinally
IL_0051: nop

注意到它是Monitor.Exit的参数是ldloc.1,也就是之前使用lock的方法内声明的object变量,这个保证了即使lock代码段内对t进行了各种操作,也不会造成如Monitor那样的异常。
Anod 2012-07-11
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 的回复:]
引用 2 楼 的回复:

引用 1 楼 的回复:
只能Monitor引用类型,不能是值类型

C# code

public partial class Form1 : Form
{
private ThreadStart myStart;
private ParameterizedThreadStart myPStart;
int m, n, s =……

你装箱拆箱后……
[/Quote]

你的意思是装箱后,Lock和Monitor.Enter的t不是同一个?这个很难去理解。装箱的IL代码是一样的,

IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: ldfld int32 WindowsApplication1.Form1::s
IL_0008: box [mscorlib]System.Int32
IL_000d: stfld object WindowsApplication1.Form1::t
,根据MSDN对装箱的定义,s被装箱后会被创建一个新的实例存储在t中,所以我觉得这里t是没有变的。
再看Lock开始时的代码:
     IL_0005:  nop        
IL_0006: ldc.i4.s 100
IL_0008: call void [mscorlib]System.Threading.Thread::Sleep(int32)
IL_000d: nop
IL_000e: nop
IL_000f: ldarg.0
IL_0010: ldfld object WindowsApplication1.Form1::t
IL_0015: dup
IL_0016: stloc.1

IL_0017: call void [mscorlib]System.Threading.Monitor::Enter(object)
IL_001c: nop

Monitor.Enter的IL代码少了红色部分,红色部分的意思是执行时会复制t并存储在使用lock的方法起始时声明的一个object变量中。而Monitor是没有的。


呃,在准备复制下面代码的时候我明白了,就复制上来吧,免得以后又忘了:

IL_0045: stloc.0
IL_0046: ldarg.0
IL_0047: ldloc.0
IL_0048: box [mscorlib]System.Int32
IL_004d: stfld object WindowsApplication1.Form1::t

Monitor的线程在使用t中的数据后会进行装箱,这时stfld指令会将装箱后生成的不同引用传给t,从而导致Enter和Pulse、Exit的不一致,使得线程出现异常。

呵呵,原问题解决了,新问题出来了,


lock的代码段退出很简单,直接Leave掉
      IL_0052:  nop        
IL_0053: leave.s IL_0058
IL_0055: nop
IL_0056: nop
IL_0057: endfinally

而Monitor的:
 IL_0048:  box        [mscorlib]System.Int32
IL_004d: stfld object WindowsApplication1.Form1::t
IL_0052: ldarg.0
IL_0053: ldfld object WindowsApplication1.Form1::t
IL_0058: call void [mscorlib]System.Threading.Monitor::Pulse(object)
IL_005d: nop
IL_005e: nop
IL_005f: leave.s IL_0070
IL_0061: nop
IL_0062: ldarg.0
IL_0063: ldfld object WindowsApplication1.Form1::t
IL_0068: call void [mscorlib]System.Threading.Monitor::Exit(object)
IL_006d: nop
IL_006e: nop
IL_006f: endfinally

IL_0070: nop
IL_0071: ldarg.0
IL_0072: dup
IL_0073: ldfld int32 WindowsApplication1.Form1::m
IL_0078: ldc.i4.1
IL_0079: add
IL_007a: stfld int32 WindowsApplication1.Form1::m

为什么Leave指令把endfinally 也给跳过了,这不相当于是出现了异常但没有执行finally块吗?我的程序有错,但也不应该这里有错吧,真心不明白!!!

111,097

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧