0

I have a C# console app. This app is called by several different other apps, but the code inside must only be executed once only by the first caller.

The second caller needs to wait for the first caller to finish, and than NOT execute the code.

I'm trying to use the Mutex object with something with the WaitOne, so waiting for a release is simple, but than the second caller should skip the code within the Mutex...

Dennis
  • 1,810
  • 3
  • 22
  • 42

2 Answers2

2

Mutex will work, but must be used correctly:

static void Main(string[] args)
{
    bool createdNew;
    Mutex mutex = new Mutex(true, "TestSO27835942", out createdNew);

    if (createdNew)
    {
        Console.WriteLine("First process!");
    }
    else
    {
        Console.WriteLine("Second process...waiting for first process");
        mutex.WaitOne();
        Console.WriteLine("First process has completed");
    }

    Console.WriteLine("Press return to exit...");
    Console.ReadLine();
    mutex.ReleaseMutex();
}

This code creates or opens an existing named Mutex. The first process to execute the code will create the Mutex, and the createdNew variable will be set to true. The second (or any subsequent) process will simply open the existing named Mutex, and the createdNew variable will be set to false.

Importantly, the first process will also acquire the Mutex as part of the creation. This ensures that no other process can acquire the Mutex before it. Then any subsequent process may attempt to acquire the Mutex, which will can that process to wait until it's available.

Finally note that after the first process, there is no specific ordering. The first process, the one that gets to create the Mutex, will always acquire it first. But after that, it just depends on how Windows schedules the processes. They will all get their turn though.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
0

Below is a complete example using Mutex, where one can add money an withdraw it from an account.

Note a few things:

  • In the constructor is shown a few steeps which would avoid security problems when using Mutex. Moreover, It shows how to correctly name a Global Mutex.
  • AFter mutex.WaitOne(MUTEX_WAIT_TIMEOUT, false); is called, not that it is verified whether the lock was acquired. If it was, then the code proceeds normaly, otherwise a TimeoutException is thrown. This is a nice practice so you can detect when Timeout has occured in your Mutex.
  • This, by far, is the most important observation: note that the mutex.ReleaseMutex(); code is called in a finally clause. By releasing the Mutex in a finally clause, you guarantee that, if an unexpected exception occurs, the Mutex will be released anyway. If you do not do so in the finally clause and an unexpected exception is thrown, before you Mutex is release, the code may be locked undefinately and you program may simply freeze.

Please, check this nice post (it is in Portuguese though) for an interesting comparison between, Mutex, the Monitor design pattern, and the lock construct.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;

namespace MutexArticle
{
    class BankAccountMutex
    {
        private double bankMoney = 0d;

        Mutex mutex = null;

        private const int MUTEX_WAIT_TIMEOUT = 5000;

        // Note: configuration based on stackoverflow answer: http://stackoverflow.com/questions/229565/what-is-a-good-pattern-for-using-a-global-mutex-in-c
        public BankAccountMutex(double money)
        {
            // get application GUID as defined in AssemblyInfo.cs
            string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();

            // unique id for global mutex - Global prefix means it is global to the machine
            string mutexId = string.Format("Global\\{{{0}}}", appGuid);

            // Need a place to store a return value in Mutex() constructor call
            bool createdNew;

            // set up security for multi-user usage
            // work also on localized systems (don't use just "Everyone") 
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);

            mutex = new Mutex(false, mutexId, out createdNew, securitySettings);

            LogConsole("Setting initial amount of money: " + money);

            if (money < 0)
            {
                LogConsole("The entered money quantity cannot be negative. Money: " + money);
                throw new ArgumentException(GetMessageWithTreadId("The entered money quantity cannot be negative. Money: " + money));
            }

            this.bankMoney = money;
        }

        public void AddMoney(double money = 0) 
        {
            bool hasHandle = mutex.WaitOne(MUTEX_WAIT_TIMEOUT, false);

            if (!hasHandle)
            {
                throw new TimeoutException(GetMessageWithTreadId("Method void AddMoney(double): Timeout due to look waiting."));
            }

            try
            {
                LogConsole("Money to be added: " + money);

                if (money < 0)
                {
                    LogConsole("The entered money quantity cannot be negative. Money: " + money);
                    throw new ArgumentException(GetMessageWithTreadId("The entered money quantity cannot be negative. Money: " + money));
                }

                this.bankMoney = this.bankMoney + money;

                if (this.bankMoney < 0)
                {
                    LogConsole("The money quantity cannot be negative. Money: " + money);
                    throw new ArgumentException(GetMessageWithTreadId("The money quantity cannot be negative. Money: " + money));
                }

                LogConsole("Total amount of money: " + this.bankMoney);
            }
            finally
            {
                if (hasHandle)
                {
                    mutex.ReleaseMutex();
                }
            }
        }

        public void WithdrawMoney(double money = 0)
        {
            bool hasHandle = mutex.WaitOne(MUTEX_WAIT_TIMEOUT, false);

            if (!hasHandle)
            {
                throw new TimeoutException(GetMessageWithTreadId("Method void WithdrawMoney(double): Timeout due to look waiting."));
            }

            try
            {

                if (money < 0)
                {
                    LogConsole("The entered money quantity cannot be negative. Money: " + money);
                    throw new ArgumentException(GetMessageWithTreadId("The entered money quantity cannot be negative. Money: " + money));
                }

                LogConsole("Money to be withdrawed: " + money);

                this.bankMoney = this.bankMoney - money;

                if (this.bankMoney < 0)
                {
                    LogConsole("The money quantity cannot be negative. Money: " + money);
                    throw new ArgumentException(GetMessageWithTreadId("The money quantity cannot be negative. Money: " + money));
                }

                LogConsole("Total amount of money: " + this.bankMoney);
            }
            finally
            {
                if (hasHandle)
                {
                    mutex.ReleaseMutex();
                }
            }
        }

        public double GetBankStatement()
        {
            LogConsole("Bank Statement: Total amount of money: " + this.bankMoney);
            return bankMoney;
        }

        private String getCurrenThreadId()
        {
            return Thread.CurrentThread.ManagedThreadId.ToString();
        }

        private void LogConsole(String message)
        {
            Console.WriteLine(GetMessageWithTreadId(message));
        }

        private String GetMessageWithTreadId(String message)
        {
            return "Thread ID: " + getCurrenThreadId() + ": " + message;
        }
    }
}
EduardoFernandes
  • 3,091
  • 1
  • 13
  • 12