Let's say I have two methods in my class MethodA
and MethodB
. Those methods are run in async, and I don't want that they run at the same time. In the app, MethodA
is called by the user, but MethodB
is run by a Timer in a background thread.
How I see the implementation in C#ish pseudocode:
class MyClass
{
private static object _lock = new object();
public async Task MethodA()
{
lock(_lock)
{
await DoSomeStuff();
}
}
public async Task MethodB()
{
if(Monitor.IsEntered(_lock)
return;
lock(_lock)
{
await DoSomeStuff();
}
}
}
So first question is - is above approach correct? I guess that's more a question for https://codereview.stackexchange.com/ .
So the second questions is - assuming that the approach is correct, how I can unit test, that MethodA
waits for MethodB
, and that MethodB
doesn't run when MethodA
is running? Or how can I refactor it so that's testable?
EDIT: accordingly to comments, changed from using flags to a lock.
Boolean flags are the obsolete way to synchronize two threads. It causes race conditions when one thread can read a value of false
while other thread is updating the value to true
;
Since your case it not straightforward (B shouldn't way for B to end, while A should wait), then I would change the class use a Semaphore
like this:
public class MyClass
{
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
public async Task MethodA()
{
await semaphore.WaitAsync();
await DoSomeStuff();
semaphore.Release();
}
public async Task MethodB()
{
bool success = await semaphore.WaitAsync(1);
if (!success)
return;
await DoSomeStuff();
await Task.Delay(TimeSpan.FromSeconds(5));
semaphore.Release();
}
}
I would consider putting all that in try..catch..finally
block and release the semaphore in the finally block, but i'm trying to keep it simple while you can add that yourself.
This is not straight forward to test. Taking threads into account, you might need to repeat the test multiple times to reach a case of failure. You might need to introduce an overload for method A that waits for some times, which might prove that method B is waiting for it. Here is the test. To test the case of failure, change new SemaphoreSlim(1);
to new SemaphoreSlim(2);
and the test would fail because MethodB
would start before MethodA
ends.
[TestMethod]
public async Task TestingMyClassThreadSync()
{
int repeatTimes = 100;
int counter = 0;
while (counter++ <= repeatTimes)
{
MyClass myClass = new MyClass();
Task tA = myClass.MethodA();
Task tB = myClass.MethodB();
Task finishedTask = await Task.WhenAny(tA, tB);
bool bFinishedBeforeA = finishedTask == tA;
if (bFinishedBeforeA)
Assert.Fail();
}
}
I would introduce an overload:
public async Task MethodA(long waitMilliseconds = 0)
{
await semaphore.WaitAsync();
await DoSomeStuff();
await Task.Delay(waitMilliseconds);
semaphore.Release();
}
Then Call it from unit testing as MethodA(5000);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.