使用C#中的循环中的lock语句 [英] Using lock statement within a loop in C#

查看:332
本文介绍了使用C#中的循环中的lock语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们示例类SomeThread当我们试图阻止正在运行的属性设置为false后处置由OtherThread类调用,因为如果他们Dispose方法后调用被调用DoSomething的方法是世界上会最后,因为我们知道这一点。



这感觉好像有什么东西邪恶,因为循环发生的机会。这在它开始下一个循环,并锁在调用DoSomething的方法之前拍摄前,跑步可以改变为false,并且它击中锁之前处理完毕调用点。在这种情况下生活会不会好。



我看着方式简单易于操作使用一个循环,以保持方法时要处理这个问题。为了记录我也认为是双锁检查图案,但它是似乎并没有被推荐为C#。



警告:这是一个简单的例子,以尽量做到便于集中与循环问题和一个内锁定。如果我没有详细说明一些不足的地方,请让我知道,我会尽我所能来填充任何细节。



 公共类SomeThread:IDisposable的
{
私有对象更衣室=新的对象();
私人布尔运行= FALSE;

公共BOOL运行
{
得到
{
锁(柜)
{
返回运行;
}
}

{
锁(柜)
{
上运行=价值;
}
}
}

公共无效的run()
{
,而(运行)
{
锁(柜)
{
DoSomething1();
了doSomething2();
}
}
}

私人无效DoSomething1()
{
//真棒事情发生在这里
}

私人无效了doSomething2()
{
//更多的东西真棒发生在这里
}

公共无效的Dispose()
{
锁(柜)
{
Dispose1();
Dispose2();
}
}

私人无效Dispose1()
{
//真棒事情发生在这里
}

私人无效Dispose2()
{
//更多的东西真棒发生在这里
}

}

公共类OtherThread
{
SomeThread ST =新SomeThread();

公共无效OnQuit()
{
st.Running = FALSE;
st.Dispose();

出口();
}
}


解决方案

拿退一步。



开始通过指定所有的期望和非期望的特性之前的你开始写一个解决方案。一些可以立即浮现在脑海中:




  • 的工作是在线程W.完成的UI上线U.完成

  • 的工作是在工作单位完成的。每个工作单元是持续时间短,为短的一些定义。让我们调用做的工作M()方法。

  • 的工作是不断地用W做的,在一个循环,直到你告诉它停止。

  • U的调用清理方法,D(),当所有的工作已经完成。

  • D()不得以前或在M()运行期间运行。

  • 退出()必须调用D()后,在螺纹U.

  • ü必须从不为一个长时间块;这是可以接受的它阻止一个短的时间。

  • 无死锁,等等。



这是否总结问题空间?



首先,我注意到,它的看起来的第一眼的问题,就是你必须为D的调用者()。当w分别为D()的调用者,那么你就不用担心;你只是信号W打出来的循环,和则W将在循环后调用D()。但是,这只是换了一个又一个的问题;想必在这种情况下,U必须等待W至调用D()U调用exit()之前。因此,移动电话。从U至W D()实际上并没有使问题更容易。



您曾经说过,你不希望使用双检查锁定。你应该知道,作为CLR V2的,双重检查锁定模式被称为是安全的。内存模型担保v2中得到加强。因此,它可能是安全供您使用双重检查锁定。



更​​新:你问的:(1)为什么双重检查锁定在V2安全的,但不是在V1的信息? (2)为什么我用黄鼠狼词可能?



要了解为什么双重检查锁定在CLR V1内存模型不安全,但在安全CLR V2内存模型,请阅读本:



HTTP ://msdn.microsoft.com/en-us/magazine/cc163715.aspx



我说可能,因为乔·达菲说明智:




一旦你创业的为数不多的福地
无锁的做法[界限,甚至微微外展
。 ..]你
打开自己可达竞争条件最差的一种




我不知道知道,如果你正在使用计划的双核对正确锁定,或者如果你对双重检查锁定,其实可怕的死亡在IA64机器编写自己的聪明,破碎变化规划。因此,它会的可能的为你工作,如果你的问题实际上是经得起仔细检查了锁的的正确编写的代码。



如果你关心这个你应该读乔·达菲的文章:



http://www.bluebytesoftware.com/blog/2006/01/26/BrokenVariantsOnDoublecheckedLocking.aspx



以及



HTTP:// WWW。 bluebytesoftware.com/blog/2007/02/19/RevisitedBrokenVariantsOnDoubleCheckedLocking.aspx



和这太问题有一些很好的讨论:



HTTP: //stackoverflow.com/questions/1964731/the-need-for-volatile-modifier-in-double-checked-locking-in-net



也许这是最好找比双重检查锁定其它一些其他机制



有是等待一个线程被关闭,完成一个机制 - 线程。加入。你可以加入从UI线程的工作线程;当工作线程关闭,在UI线程再次醒来并做了Dispose



更​​新:增加了关于加入一些信息



加入基本上意味着线程u指示线W至关机,和U进入睡眠状态,直到发生这种情况。 Quit方法的简要素描:

  //在您选择
上正在运行的线程安全的方式做到这一点=假;
//等待工作线程打住
workerThread.Join();
//现在我们知道,工人线程完成,所以我们可以
//清理并退出
的Dispose();
出口();



假如你不希望使用加入出于某种原因。 (也许工作者线程需要保持以做别的事情上运行,但你仍然需要在它使用的对象做就知道了。)我们可以建立我们自己的机制,它的工作原理是通过使用等待句柄加入。你现在需要的是什么的两个的锁定机制:一个让你寄一个信号为W,说:现在停止运行,然后另一个等待的而W则完成了最后调用M()。



我想在这种情况下做的是:




  • 请一个线程安全标志跑。使用您熟悉使它线程安全的任何机制。我个人开始与专门给它锁;如果你以后决定,你可以用它无锁联锁操作去,那么你总是可以做的后来。

  • 作出的AutoResetEvent来充当的Dispose门。



所以,简单的素描:



UI线程,启动逻辑:

 运行= TRUE 
的WaitHandle =新的AutoResetEvent(假)
启动工作线程

UI线程,退出的逻辑:

 运行= FALSE; //在您选择
waithandle.WaitOne的线程安全的方式做到这一点();

// WaitOne的是在比赛条件下,面对强劲;如果工作线程
//调用设置* *前的WaitOne被调用,WaitOne的将是一个空操作。 (但是,
//如果有*多*线程都尝试唤醒栅极即
//等待WaitOne的,所述多个唤醒将丢失。WaitOne的被命名为$ B $ ; b // WaitOne的,因为它会等待一个唤醒如果您需要等待多个
//唤醒,不要使用WaitOne的

的Dispose();
的WaitHandle。关闭();
出口();

工作线程:

 而(运行)//使线程安全的获得运行
M();
waithandle.Set() ; //告诉等待UI线程是安全处置

注意,这依赖于一个事实在于M ()是短暂的。如果M()需要很长的时间,那么你可以等待很长时间才能退出应用程序,这似乎是坏的。



这是否有意义?



真的不过,你不应该这样做。如果你想等待工作线程关闭你处置它使用一个对象之前,刚刚加入。



更新:一些额外的问题提出:




是一个好主意等待没有超时?




事实上,请注意,我用加入例如我与WaitOne的例子,我不上使用的变种他们等待放弃之前的特定时间量。相反,我打电话,我的假设是,工作线程干净并迅速关闭。这是做正确的事?



这要看!这取决于工作线程是多么的糟糕表现,以及当它行为不端它做。



如果你能保证工作持续时间短,无论出于何种短是指给你,那么你并不需要一个暂停。如果你不能保证,那么我会建议先重写代码,使您的可以的保证;生活变得,如果你知道,当你问它的代码将很快结束容易得多。



如果你不能,那么什么是应该做的事情吗?这种情况下的假设是,工人病态行为,当问及时不会终止。所以,现在我们必须问自己是工人的设计慢的,或敌对的?



在第一种情况下,工作人员只是做一些需要较长时间,不管出于什么原因,不能中断。什么是在这里做正确的事情?我不知道。这是一个可怕的情况是,大概是工人不能很快关闭,因为这样做是很危险的或不可能的。在这种情况下,你打算怎么办时,超时超时?你已经得到的东西是危险或不可能关闭,它不是及时关停。您的选择似乎是(1)什么也不做,(2)做一些危险,或者(3)做一些事情是不可能的。选择三是可能的。任选其一相当于永远等待,whcih我们已经拒绝。 。这使得做一些危险的



知道什么是正确的事情,以尽量减少危害用户数据做取决于所造成的危险的确切情况;仔细分析一下,了解所有的场景,并找出做正确的事情。



现在假设工人应该是能够迅速关闭,但不因为它有一个错误。很显然,如果可以的话,修复bug。如果你不能修复bug - 也许是在代码中你没有自己 - 话又说回来,你是在焦头烂额。你必须明白什么后果是不是在等待业已越野车和-因此,不可预知的代码,您知道它是使用权,现在在另一个线程的资源处置前完成。而且你要知道后果是同时马车工作线程仍忙于天堂终止应用程序的唯一东西知道什么操作系统状态。



如果代码的敌对的,是的积极抵抗被关闭的,那么你已经失去了。您无法通过正常手段阻止线程,你甚至不能线程终止它。有没有保证,中止敌对线程实际终止它;该恶意代码的你已经开始愚蠢在运行过程中车主可以做在防止线程中止异常finally块或其他限制的区域其所有的工作。



做的最好的事情是永远也进不了这个情况摆在首位;如果你有,你认为是敌对的,要么不运行它在所有的,或者在自己的进程运行,并终止的过程的,不是的线程的,当事情码搞砸。



在短,没有很好地回答了这个问题:如果时间过长怎么办?你是在一个的可怕的情况下,如果出现这种情况并没有简单的答案。最好的努力,以确保你没有进入它摆在首位;只运行合作,良性的,安全的代码,当被问及总是自行关闭干净,快速。




如果有什么工人抛出一个异常?




确定,所以如果它呢?再次,最好不要在这种情况下,摆在首位;写工人代码,以便它不会抛出。如果你不能做到这一点,那么你有两种选择:处理异常,或者不处理异常。



假设你不处理异常。由于我认为CLR V2,在辅助线程未处理的异常关闭整个应用程序。其原因是,在过去会发生什么事是你启动了一堆工作线程,他们就都抛出异常,而且你会最终有一个运行的应用程序没有工作线程离开,做任何工作,没有告诉它的用户。这是更好地强制代码来处理情况,工作线程出现故障,由于异常情况的作者;做旧的方式有效地隐藏缺陷,使得它很容易编写脆弱的应用程序。



假设你做处理异常。怎么办?东西抛出一个异常,这是定义一个意外的错误情况。你现在有完全不知道自己的任何您的数据是一致的,或任何你的程序不变量都保存在您的任何子系统。那么,你打算怎么办?有几乎没有什么安全的,你可以在这一点上做的。



现在的问题是什么是最适合在这种不幸的情况,用户?这取决于什么应用程序正在做的事情。这是完全有可能的最好的事情在这一点上做的就是积极地关闭并告诉意想不到的事情失败的用户。这可能是比试图蒙混过关并可能使情况变得更糟,靠,比如,不小心破坏用户数据,而试图清理好。



或者,这完全是可能是做的最好的事情就是做一个真诚的努力来保护用户的数据,整理尽可能多状态越好,因为正常结束越好。



基本上,无论你的问题是我该怎么办时,我子系统不循规蹈矩?如果您的子系统是不可靠的,无论是的使它们可靠对你怎样面对一个不可靠的子系统政策,并实施政策的。这是一个模糊的答案,我知道,但是那是因为处理一个不可靠的子系统本质上是一个可怕的情况是,你怎么处理它取决于其不可靠的性质,而且不可靠用户的宝贵数据所带来的后果。


Lets take the sample class SomeThread where we are attempting to prevent the DoSomething methods from being called after the Running property is set to false and Dispose is called by the OtherThread class because if they are called after the Dispose method is the world would end as we know it.

It feels like there is a chance for something evil to happen because of the loop. That at the point where it starts the next loop and before the lock is taken before calling the DoSomething methods, Running could be changed to false, and Disposed called before it hits the lock. In this scenario life would not be good.

I was looking at ways to handle this when using a loop in a simple easy to maintain method. For the record I did considered the Double Lock Check patterned, however it is does not seem to be recommend for C#.

Warning: This is a simplified example to try to make it easy to focus on the issue with the loop and locking within one. If I didn't elaborate enough some place please let me know and I will do my best to fill in any details.

public class SomeThread : IDisposable
{
    private object locker = new object();
    private bool running = false;

    public bool Running 
    { 
        get
        {
            lock(locker)
            {
                return running;
            }
        }
        set
        {
            lock(locker)
            {
                running = value;
            }
        }
    }

    public void Run()
    {
        while (Running)
        {
            lock(locker)
            {
                DoSomething1();
                DoSomething2();
            }
        }
    }

    private void DoSomething1()
    {
        // something awesome happens here
    }

    private void DoSomething2()
    {
        // something more awesome happens here
    }

    public void Dispose()
    {
        lock (locker)
        {   
            Dispose1();
            Dispose2();
        }
    }

    private void Dispose1()
    {
        // something awesome happens here
    }

    private void Dispose2()
    {
        // something more awesome happens here
    }

}

public class OtherThread
{
    SomeThread st = new SomeThread();

    public void OnQuit()
    {
        st.Running = false;
        st.Dispose();

        Exit();
    }
}

解决方案

Take a step back.

Start by specifying all the desirable and undesirable characteristics before you start to write a solution. A few that come immediately to mind:

  • The "work" is done on thread W. The "UI" is done on thread U.
  • The work is done in "units of work". Each unit of work is "short" in duration, for some definition of "short". Let's call the method that does the work M().
  • The work is done continuously by W, in a loop, until U tells it to stop.
  • U calls a cleanup method, D(), when all the work is done.
  • D() must not ever run before or while M() is running.
  • Exit() must be called after D(), on thread U.
  • U must never block for a "long" time; it is acceptable for it to block for a "short" time.
  • No deadlocks, and so on.

Does this sum up the problem space?

First off, I note that it seems at first glance that the problem is that U must be the caller of D(). If W were the caller of D(), then you wouldn't have to worry; you'd just signal W to break out of the loop, and then W would call D() after the loop. But that just trades one problem for another; presumably in this scenario, U must wait for W to call D() before U calls Exit(). So moving the call to D() from U to W doesn't actually make the problem easier.

You've said that you don't want to use double-checked locking. You should be aware that as of CLR v2, the double-checked locking pattern is known to be safe. The memory model guarantees were strengthened in v2. So it is probably safe for you to use double-checked locking.

UPDATE: You asked for information on (1) why is double-checked locking safe in v2 but not in v1? and (2) why did I use the weasel-word "probably"?

To understand why double-checked locking is unsafe in the CLR v1 memory model but safe in the CLR v2 memory model, read this:

http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

I said "probably" because as Joe Duffy wisely says:

once you venture even slightly outside of the bounds of the few "blessed" lock-free practices [...] you are opening yourself up to the worst kind of race conditions.

I do not know if you are planning on using double-checked locking correctly, or if you're planning on writing your own clever, broken variation on double-checked locking that in fact dies horribly on IA64 machines. Hence, it will probably work for you, if your problem is actually amenable to double checked locking and you write the code correctly.

If you care about this you should read Joe Duffy's articles:

http://www.bluebytesoftware.com/blog/2006/01/26/BrokenVariantsOnDoublecheckedLocking.aspx

and

http://www.bluebytesoftware.com/blog/2007/02/19/RevisitedBrokenVariantsOnDoubleCheckedLocking.aspx

And this SO question has some good discussion:

http://stackoverflow.com/questions/1964731/the-need-for-volatile-modifier-in-double-checked-locking-in-net

Probably it is best to find some other mechanism other than double-checked locking.

There is a mechanism for waiting for one thread which is shutting down to complete -- thread.Join. You could join from the UI thread to the worker thread; when the worker thread is shut down, the UI thread wakes up again and does the dispose.

UPDATE: Added some information on Join.

"Join" basically means "thread U tells thread W to shut down, and U goes to sleep until that happens". Brief sketch of the quit method:

// do this in a thread-safe manner of your choosing
running = false; 
// wait for worker thread to come to a halt
workerThread.Join(); 
// Now we know that worker thread is done, so we can 
// clean up and exit
Dispose(); 
Exit();   

Suppose you didn't want to use "Join" for some reason. (Perhaps the worker thread needs to keep running in order to do something else, but you still need to know when it is done using the objects.) We can build our own mechanism that works like Join by using wait handles. What you need now are two locking mechanisms: one that lets U send a signal to W that says "stop running now" and then another that waits while W finishes off the last call to M().

What I would do in this circumstance is:

  • make a thread-safe flag "running". Use whatever mechanism you are comfortable with to make it thread safe. I would personally start with a lock dedicated to it; if you decide later that you can go with lock-free interlocked operations on it then you can always do that later.
  • make an AutoResetEvent to act as a gate on the dispose.

So, brief sketch:

UI thread, startup logic:

running = true
waithandle = new AutoResetEvent(false)
start up worker thread

UI thread, quit logic:

running = false; // do this in a thread-safe manner of your choosing
waithandle.WaitOne(); 

// WaitOne is robust in the face of race conditions; if the worker thread
// calls Set *before* WaitOne is called, WaitOne will be a no-op.  (However,
// if there are *multiple* threads all trying to "wake up" a gate that is
// waiting on WaitOne, the multiple wakeups will be lost. WaitOne is named
// WaitOne because it WAITS for ONE wakeup. If you need to wait for multiple
// wakeups, don't use WaitOne.

Dispose();
waithandle.Close();
Exit();    

worker thread:

while(running) // make thread-safe access to "running"
    M();
waithandle.Set(); // Tell waiting UI thread it is safe to dispose

Notice that this relies on the fact that M() is short. If M() takes a long time then you can wait a long time to quit the application, which seems bad.

Does that make sense?

Really though, you shouldn't be doing this. If you want to wait for the worker thread to shut down before you dispose an object it is using, just join it.

UPDATE: Some additional questions raised:

is it a good idea to wait without a timeout?

Indeed, note that in my example with Join and my example with WaitOne, I do not use the variants on them that wait for a specific amount of time before giving up. Rather, I call out that my assumption is that the worker thread shuts down cleanly and quickly. Is this the correct thing to do?

It depends! It depends on just how badly the worker thread behaves and what it is doing when it is misbehaving.

If you can guarantee that the work is short in duration, for whatever 'short' means to you, then you don't need a timeout. If you cannot guarantee that, then I would suggest first rewriting the code so that you can guarantee that; life becomes much easier if you know that the code will terminate quickly when you ask it to.

If you cannot, then what's the right thing to do? The assumption of this scenario is that the worker is ill-behaved and does not terminate in a timely manner when asked to. So now we've got to ask ourselves "is the worker slow by design, buggy, or hostile?"

In the first scenario, the worker is simply doing something that takes a long time and for whatever reason, cannot be interrupted. What's the right thing to do here? I have no idea. This is a terrible situation to be in. Presumably the worker is not shutting down quickly because doing so is dangerous or impossible. In that case, what are you going to do when the timeout times out??? You've got something that is dangerous or impossible to shut down, and its not shutting down in a timely manner. Your choices seem to be (1) do nothing, (2) do something dangerous, or (3) do something impossible. Choice three is probably out. Choice one is equivalent to waiting forever, whcih we've already rejected. That leaves "do something dangerous".

Knowing what the right thing to do in order to minimize harm to user data depends upon the exact circumstances that are causing the danger; analyse it carefully, understand all the scenarios, and figure out the right thing to do.

Now suppose the worker is supposed to be able to shut down quickly, but does not because it has a bug. Obviously, if you can, fix the bug. If you cannot fix the bug -- perhaps it is in code you do not own -- then again, you are in a terrible fix. You have to understand what the consequences are of not waiting for already-buggy-and-therefore-unpredictable code to finish before disposing of the resources that you know it is using right now on another thread. And you have to know what the consequences are of terminating an application while a buggy worker thread is still busy doing heaven only knows what to operating system state.

If the code is hostile and is actively resisting being shut down then you have already lost. You cannot halt the thread by normal means, and you cannot even thread abort it. There is no guarantee whatsoever that aborting a hostile thread actually terminates it; the owner of the hostile code that you have foolishly started running in your process could be doing all of its work in a finally block or other constrained region which prevents thread abort exceptions.

The best thing to do is to never get into this situation in the first place; if you have code that you think is hostile, either do not run it at all, or run it in its own process, and terminate the process, not the thread when things go badly.

In short, there's no good answer to the question "what do I do if it takes too long?" You are in a terrible situation if that happens and there is no easy answer. Best to work hard to ensure you don't get into it in the first place; only run cooperative, benign, safe code that always shuts itself down cleanly and rapidly when asked.

What if the worker throws an exception?

OK, so what if it does? Again, better to not be in this situation in the first place; write the worker code so that it does not throw. If you cannot do that, then you have two choices: handle the exception, or don't handle the exception.

Suppose you don't handle the exception. As of I think CLR v2, an unhandled exception in a worker thread shuts down the whole application. The reason being, in the past what would happen is you'd start up a bunch of worker threads, they'd all throw exceptions, and you'd end up with a running application with no worker threads left, doing no work, and not telling the user about it. It is better to force the author of the code to handle the situation where a worker thread goes down due to an exception; doing it the old way effectively hides bugs and makes it easy to write fragile applications.

Suppose you do handle the exception. Now what? Something threw an exception, which is by definition an unexpected error condition. You now have no clue whatsoever that any of your data is consistent or any of your program invariants are maintained in any of your subsystems. So what are you going to do? There's hardly anything safe you can do at this point.

The question is "what is best for the user in this unfortunate situation?" It depends on what the application is doing. It is entirely possible that the best thing to do at this point is to simply aggressively shut down and tell the user that something unexpected failed. That might be better than trying to muddle on and possibly making the situation worse, by, say, accidentally destroying user data while trying to clean up.

Or, it is entirely possible that the best thing to do is to make a good faith effort to preserve the user's data, tidy up as much state as possible, and terminate as normally as possible.

Basically, both your questions are "what do I do when my subsystems do not behave themselves?" If your subsystems are unreliable, either make them reliable, or have a policy for how you deal with an unreliable subsystem, and implement that policy. That's a vague answer I know, but that's because dealing with an unreliable subsystem is an inherently awful situation to be in. How you deal with it depends on the nature of its unreliability, and the consequences of that unreliability to the user's valuable data.

这篇关于使用C#中的循环中的lock语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆