对象同步方法是从未同步的代码块调用的.互斥释放()出现异常

本文关键字:释放 异常 同步方法 同步 对象 代码 调用 | 更新日期: 2023-09-27 18:21:40

我发现了关于这个异常的不同文章,但没有一篇是我的情况。这是源代码:

class Program
{
    private static Mutex mutex;
    private static bool mutexIsLocked = false;
    static void Main(string[] args)
    {
        ICrmService crmService = 
            new ArmenianSoftware.Crm.Common.CrmServiceWrapper(GetCrmService("Armsoft", "crmserver"));
        //Lock mutex for concurrent access to workflow
        mutex = new Mutex(true, "ArmenianSoftware.Crm.Common.FilterCtiCallLogActivity");
        mutexIsLocked = true;
        //Create object for updating filtered cti call log
        ArmenianSoftware.Crm.Common.FilterCtiCallLog filterCtiCallLog =
            new ArmenianSoftware.Crm.Common.FilterCtiCallLog(crmService);
        //Bind events
        filterCtiCallLog.CtiCallsRetrieved += new EventHandler<ArmenianSoftware.Crm.Common.CtiCallsRetrievedEventArgs>(filterCtiCallLog_CtiCallsRetrieved);
        //Execute filter
        try
        {
            filterCtiCallLog.CreateFilteredCtiCallLogSync();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (mutexIsLocked)
            {
                mutexIsLocked = false;
                mutex.ReleaseMutex();
            }
        }
    }
    static void filterCtiCallLog_CtiCallsRetrieved(object sender,
         ArmenianSoftware.Crm.Common.CtiCallsRetrievedEventArgs e)
    {
        tryasasas
        {
            if (mutexIsLocked)
            {
                mutexIsLocked = false;
                mutex.ReleaseMutex();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

filterCtiCallLog.CreateFilteredCtiCallLogSync();函数执行对服务器的请求,并引发一些事件,其中之一就是CtiCallsRetrieve事件。当这个事件被触发时,我需要释放互斥锁。但是在调用互斥对象时。Release()函数引发异常。CCD_ 3同步工作。问题出在哪里?

对象同步方法是从未同步的代码块调用的.互斥释放()出现异常

保留一个表示互斥对象已拥有的bool是一个严重错误。你没有保证底线的安全。您之所以陷入这种困境,是因为您使用了错误的同步对象。互斥体具有线程亲和性,互斥体的所有者是线程。获取它的线程也必须是调用ReleaseMutex()的线程。这就是你的代码爆炸的原因。

您很可能需要事件,请使用AutoResetEvent。在主线程中创建它,在工作线程中调用Set(),在主线程调用WaitOne()以等待工作线程完成其作业。然后处理掉。还要注意,使用线程执行作业并让主线程等待其完成是没有效率的。你还不如让主线程来完成这项工作。

如果您这样做实际上是为了保护对非线程安全对象的访问(尚不清楚),那么请使用lock语句。

可能发生此异常的另一个原因:

if (Monitor.TryEnter(_lock))
{
    try
    {
        ... await MyMethodAsync(); ...
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}

我在监视器上得到了这个异常。在"等待"之后另一个线程继续执行时退出。

编辑:使用SemaphoreSlim,因为它不需要释放线程相同。

如果执行以下操作,您也会遇到此异常:

        mutex.WaitOne();
        … Some Work...
        await someTask;
        mutex.ReleaseMutex();

这是因为等待之后的代码可以在与前一行不同的线程上执行。基本上,如果你现在(在2020年初)异步代码,Mutexes根本不起作用。使用事件或其他东西。

我发现了问题。关于filterCtiCallLog类的前几件事。我把它设计成既能异步工作又能同步工作。首先,我编写了异步执行的代码。我需要一种方法来触发从子工作线程到父线程的事件,以报告工作状态。为此,我使用了AsyncOperation类及其post方法。这是触发CtiCallsRetried事件的代码部分。

public class FilterCtiCallLog
{
    private int RequestCount = 0;
    private AsyncOperation createCallsAsync = null;
    private SendOrPostCallback ctiCallsRetrievedPost;
    public void CreateFilteredCtiCallLogSync()
    {
        createCallsAsync = AsyncOperationManager.CreateOperation(null);
        ctiCallsRetrievedPost = new SendOrPostCallback(CtiCallsRetrievedPost);
        CreateFilteredCtiCallLog();
    }
    private void CreateFilteredCtiCallLog()
    {
        int count=0;
        //do the job
        //............
        //...........
        //Raise the event
        createCallsAsync.Post(CtiCallsRetrievedPost, new CtiCallsRetrievedEventArgs(count));
        //...........
        //...........
    }
    public event EventHandler<CtiCallsRetrievedEventArgs> CtiCallsRetrieved;
    private void CtiCallsRetrievedPost(object state)
    {
        CtiCallsRetrievedEventArgs args = state as CtiCallsRetrievedEventArgs;
        if (CtiCallsRetrieved != null)
            CtiCallsRetrieved(this, args);
    }
}

正如您所看到的,代码正在同步执行。这里的问题出现在AsyncOperation.Post()方法中。我推测,如果在主线程中调用它,它将只是触发事件,而不是将其发布到父线程。然而事实并非如此。我不知道它是如何工作的,但我已经更改了代码,以检查CreateFilteredCtiCallLog是被称为sync还是async。如果是异步调用,我使用了AsyncOperation.Post方法,如果不是,如果不是null,我只触发了EventHandler。这是修正后的代码

public class FilterCtiCallLog
{
    private int RequestCount = 0;
    private AsyncOperation createCallsAsync = null;
    private SendOrPostCallback ctiCallsRetrievedPost;
    public void CreateFilteredCtiCallLogSync()
    {
        createCallsAsync = AsyncOperationManager.CreateOperation(null);
        ctiCallsRetrievedPost = new SendOrPostCallback(CtiCallsRetrievedPost);
        CreateFilteredCtiCallLog(false);
    }
    private void CreateFilteredCtiCallLog(bool isAsync)
    {
        int count=0;
        //do the job
        //............
        //...........
        //Raise the event
        RaiseEvent(CtiCallsRetrievedPost, new CtiCallsRetrievedEventArgs(count),isAsync);
        //...........
        //...........
    }
    public event EventHandler<CtiCallsRetrievedEventArgs> CtiCallsRetrieved;
    private void RaiseEvent(SendOrPostCallback callback, object state, bool isAsync)
    {
        if (isAsync)
            createCallsAsync.Post(callback, state);
        else
            callback(state);
    }
    private void CtiCallsRetrievedPost(object state)
    {
        CtiCallsRetrievedEventArgs args = state as CtiCallsRetrievedEventArgs;
        if (CtiCallsRetrieved != null)
            CtiCallsRetrieved(this, args);
    }
}

谢谢大家的回答!

我看到过这种情况,当你使用Monitor锁定代码,然后调用异步代码时,你会得到这个,当使用锁(对象)时,你得到一个编译器错误,但是在Monitor.enter(对象)和Monitor.Exist(对象)之间,编译器不会抱怨。。。不幸地

使用标志来尝试监视内核同步对象状态是不起作用的——使用这些同步调用的目的是它们在没有任何明确检查的情况下正常工作。设置标志只会导致间歇性问题,因为在检查标志和对其进行操作之间的中断可能会不适当地更改标志

互斥锁只能由获取它的威胁释放。如果回调由其他线程(CreateFilteredCtiCallLogSync()内部的线程或内核线程池)调用,则释放将失败。

目前还不清楚你到底想做什么。大概是想序列化对CreateFilteredCtiCallLogSync()的访问以及该实例可重复使用的回调标志吗?如果是这样的话,您可以使用信号量init。将它放到一个单元中,在开始时等待它,然后在回调中释放它。

有时不调用回调,因此不调用try/finaly/release,这是否存在问题?如果是这样的话,如果回调是异步的,并且可能在设置线程离开函数后被另一个线程调用,那么这种方法似乎有点不可靠。

我只拥有过一两次,在每种情况下,它都是通过尝试释放一个我不拥有的互斥体来实现的。

您确定这些事件是在获取互斥对象的同一线程上引发的吗?尽管您提到filterCtiCallLog.CreateFilteredCtiCallLogSync()是一个阻塞调用,但它可能会产生引发事件的工作线程?

可能不是最有意义的错误消息,我在下面的一些第三方代码中看到过这种情况,

object obj = new object();
lock (obj)
{
    //do something
    Monitor.Exit(obj);//obj released
}//exception happens here, when trying to release obj

我已经阅读了这个线程并得到了一些想法。但不知道究竟需要做些什么来解决这个问题。在nopCommerce解决方案中将图像上传到s3时,我也面临同样的错误。下面的代码对我有效。

   using var mutex = new Mutex(false, thumbFileName);
         mutex.WaitOne();
          try
           {
             if (pictureBinary != null)
              {
                  try
                     {
                       using var image = SKBitmap.Decode(pictureBinary);
                       var format = GetImageFormatByMimeType(picture.MimeType);
                       pictureBinary = ImageResize(image, format, targetSize);
                     }
                   catch
                     {
                     }
             }
                
          if (s3Enabled)
           //await S3UploadImageOnThumbsAsync(thumbFileName, pictureBinary, picture.MimeType, picture, targetSize);
           // The above code was causing the issue. Because it is wait for the thread. 
           //So I replace the code below line and the error disappear. This also kind of same implementation by nopCommerce.
           //The thread need to wait.
    
           S3UploadImageOnThumbsAsync(thumbFileName, pictureBinary, picture.MimeType, picture, targetSize).Wait();
         else
            File.WriteAllBytes(thumbFilePath, pictureBinary);    
         }
         finally
         {
             mutex.ReleaseMutex();
         }  

也许编译器阻止您将awaitlock一起使用,所以您改用Monitor.EnterMonitor.Exit

第一:

lock (obj)
{
   ...
   await SomeAsyncMethod(); // compilation error
   ...
}

在认为你做这个变通方法很聪明之后:

try
{
   Monitor.Enter(obj);
   ...
   await SomeAsyncMethod(); // NOT compilation error, but runtime Excetpion !!
   ...
}
finally
{
   Monitor.Exit(obj);
}

您也不能这样做,因为lockMonitor.Enter相同,其中包含finally子句Monitor.Exit

解决方案:

使用SemaphoreSlim。例如,请查看本文的用法:https://dotnettutorials.net/lesson/semaphoreslim-class-in-csharp/