EDIT:
tl;dr
It works. The out-of-the-box solution is at the end of this answer.
/EDIT
While adding a timing sample to my question, I had the idea for a solution:
My target is to get the locks only when all of them are free (implemented below), but it could easily be changed to keeping the locks and only returning the received locks on timeout.
This part could further be moved to a static helper function which receives an array of objects (or mutexes) to lock and the timeout.
EDIT:
Done, see end of the answer.
//-------------------------------------
// using direct implementation
//-------------------------------------
void UseAllResources2 ()
{
bool bSuccess1 = false;
bool bSuccess2 = false;
try
{
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start locking 1 and 2");
DateTime tStart = DateTime::Now;
bool bSuccess = false;
do
{
bSuccess1 = Monitor::TryEnter (oResource1);
bSuccess2 = Monitor::TryEnter (oResource2);
bSuccess = bSuccess1 && bSuccess2;
if (!bSuccess)
{
if (bSuccess1) Monitor::Exit (oResource1);
if (bSuccess2) Monitor::Exit (oResource2);
Thread::Sleep(10);
}
}
while (!bSuccess && (DateTime::Now - tStart).TotalMilliseconds < msc_iTimeoutMonitor);
if (!bSuccess)
{
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() monitor timeout");
return;
}
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start work");
Thread::Sleep (400);
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() finish work");
} finally {
if (bSuccess2)
Monitor::Exit (oResource2);
if (bSuccess1)
Monitor::Exit (oResource1);
}
}
//-------------------------------------
// using Out-Of-Box solution
//-------------------------------------
static void UseAllResources3 ()
{
bool bSuccess = false;
try
{
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start locking 1 and 2");
bSuccess = MonitorTryEnter (gcnew array<Object^>{oResource1, oResource2}, 500, 10, false);
if (!bSuccess)
{
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() monitor timeout");
return;
}
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() start work");
Thread::Sleep (400);
Console::WriteLine (DateTime::Now.ToString("ss.fff") + " "__FUNCTION__"() finish work");
} finally {
if (bSuccess)
{
Monitor::Exit (oResource2);
Monitor::Exit (oResource1);
}
}
}
my main() for test:
int main()
{
// first run is for the CLR to load everything
Thread^ oThreadA = gcnew Thread (gcnew ThreadStart (&UseResource1));
Thread^ oThreadB = gcnew Thread (gcnew ThreadStart (&UseResource2));
// Thread^ oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources));
// Thread^ oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources2));
Thread^ oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources3));
oThreadB->Start();
Thread::Sleep(100);
oThreadZ->Start();
Thread::Sleep(100);
oThreadA->Start();
Thread::Sleep (2000);
Console::WriteLine();
// now that all code is JIT compiled, the timestamps are correct.
// Logs below are from this 2nd run.
oThreadA = gcnew Thread (gcnew ThreadStart (&UseResource1));
oThreadB = gcnew Thread (gcnew ThreadStart (&UseResource2));
// oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources));
// oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources2));
oThreadZ = gcnew Thread (gcnew ThreadStart (&UseAllResources3));
oThreadB->Start();
Thread::Sleep(100);
oThreadZ->Start();
Thread::Sleep(100);
oThreadA->Start();
Thread::Sleep (2000);
}
output UseAllResources(): (from question)
01.503 UseResource2() start locking
01.503 UseResource2() start work
01.604 UseAllResources() start locking 1
01.604 UseAllResources() start locking 2
01.707 UseResource1() start locking
01.903 UseResource2() finish work
01.903 UseAllResources() start work
02.211 UseResource1() monitor timeout
02.303 UseAllResources() finish work
output UseAllResources2(): (1st solution, direct implementation)
42.002 UseResource2() start locking
42.002 UseResource2() start work
42.103 UseAllResources2() start locking 1 and 2
42.206 UseResource1() start locking
42.206 UseResource1() start work
42.256 UseResource1() finish work
42.402 UseResource2() finish work
42.427 UseAllResources2() start work
42.827 UseAllResources2() finish work
output UseAllResources3(keepLocks=false): (2nd solution, out-of-box implementation)
16.392 UseResource2() start locking
16.393 UseResource2() start work
16.494 UseAllResources3() start locking 1 and 2
16.595 UseResource1() start locking
16.597 UseResource1() start work
16.647 UseResource1() finish work
16.793 UseResource2() finish work
16.818 UseAllResources3() start work
17.218 UseAllResources3() finish work
// same as previous, as expected.
output UseAllResources3(keepLocks=true): (2nd solution, out-of-box implementation)
31.965 UseResource2() start locking
31.965 UseResource2() start work
32.068 UseAllResources3() start locking 1 and 2
32.169 UseResource1() start locking
32.365 UseResource2() finish work
32.390 UseAllResources3() start work
32.672 UseResource1() monitor timeout
32.790 UseAllResources3() finish work
// timeout on thread A, as expected.
WORKS! :-)
tl;dr
Here's the out-of-box solution:
//----------------------------------------------------------------------------
// MonitorTryEnter
//----------------------------------------------------------------------------
bool MonitorTryEnter (array<Object^>^ i_aoObject, int i_iTimeout, int i_iSleep, bool i_bKeepLocks)
{
if (!i_aoObject)
return false;
if (i_iSleep < 0)
i_iSleep = 10;
List<Object^>^ listObject = gcnew List<Object^>;
for (int ixCnt = 0; ixCnt < i_aoObject->Length; ixCnt++)
if (i_aoObject[ixCnt])
listObject->Add (i_aoObject[ixCnt]);
if (listObject->Count <= 0)
return false;
array<bool>^ abSuccess = gcnew array<bool>(listObject->Count);
DateTime tStart = DateTime::Now;
bool bSuccess = true;
do
{
bSuccess = true;
if (!i_bKeepLocks)
abSuccess = gcnew array<bool>(listObject->Count);
for (int ixCnt = 0; ixCnt < listObject->Count; ixCnt++)
{
if (!abSuccess[ixCnt])
abSuccess[ixCnt] = Monitor::TryEnter (listObject[ixCnt]);
bSuccess = bSuccess && abSuccess[ixCnt];
if (!bSuccess)
break;
}
if (!bSuccess)
{
if (!i_bKeepLocks)
{
for (int ixCnt = 0; ixCnt < listObject->Count; ixCnt++)
{
if (abSuccess[ixCnt])
{
Monitor::Exit (listObject[ixCnt]);
abSuccess[ixCnt] = false;
}
}
}
Thread::Sleep(i_iSleep);
}
}
while (!bSuccess && (DateTime::Now - tStart).TotalMilliseconds < i_iTimeout);
if (!bSuccess)
{
for (int ixCnt = 0; ixCnt < listObject->Count; ixCnt++)
if (abSuccess[ixCnt])
Monitor::Exit (listObject[ixCnt]);
}
return bSuccess;
}