I am attempting to write log messages in a thread-safe fashion and a while back I heard that the best way to do that is to use the DevExpress.DXCore.Threading.SynchronizationManager class in a scenario like this:
using DevExpress.DXCore.Threading;
…
delegate void LogHandler(string title, string text);
void LogSomething(string title, string text)
{
Log.SendMsgData(title, text);
}
LogHandler handler = new LogHandler(LogSomething);
SynchronizationManager.BeginInvoke(handler, new object[] { someTitle, someText });
…where "Log" is a class like DevExpress.CodeRush.Diagnostics.ToolWindows.Log. I am attempting to follow that pattern, but I find that the SynchronizationManager doesn't run in a unit test scenario. For example, if I do this, the unit test passes:
public delegate void WriteLogMessageHandler(string message);
[TestMethod]
public void Enter_EntersLog()
{
string changed = null;
WriteLogMessageHandler del = delegate(string s)
{
changed = s;
};
IAsyncResult result = del.BeginInvoke("changed", null, null);
result.AsyncWaitHandle.WaitOne();
Assert.AreEqual("changed", changed);
}
However, if you switch the IAsyncResult line to…
IAsyncResult result = SynchronizationManager.BeginInvoke(del, new object[] {
"changed" });
…then the test never finishes. It's like the method never actually got invoked, just queued up to run.
Two questions:
- Is SynchronizationManager still the best way to do this sort of thread-safe logging or is there a better way?
- If SynchronizationManager is still good, how can I get it to run in a unit test environment?
Hi Travis,
Unfortunately, it looks as if it is impossible to use SynchronizationManager in unit tests, as its Start and Stop methods require a reference to the EnvDTE.DTE object.
We've discussed the situation you described with our developers, and we suggest that you use Console instead of the Log to output the necessary information in your unit tests.
I apologize that we can't help more in this situation.
Thanks,
Vito
The reason I asked the question was that I was trying to test plugin code that uses SynchronizationManager, not actually log things from tests. The unit test code block was just the easiest way to show something passing vs. failing. Sounds like you can't unit test calls to SynchronizationManager, which is fine (though it'd be nice if it could work in a future version).
That said, question 1 still isn't answered - should I be wrapping calls to Log objects with SynchronizationManager? Or is that overkill for what I'm doing?
As my plugin executes, possibly running a multi-threaded operation, it's going to write messages to the log. I want to make sure I'm doing that correctly and heard before (I don't remember where; forums I think) that SynchronizationManager was the way to go. Still true? Does my pattern below look correct?
Hi Travis,
I apologize for missing this part of your initial question.
Yes, the approach you're using is quite correct: you should use SynchronizationManager when performing cross-thread operations. BTW, we've already discussed this question in another report you posted:
ID: B32053, B32053
Thanks,
Vito
In case someone else wants to unit test using SynchronizationManager, here's how to do it using Typemock Isolator.
First, in your test fixture, create a private delegate that represents the method call being passed to the SynchronizationManager.
private delegate object SynchronizationManagerMethodCall(object[] parameters);
Next, you're going to make a static method that returns an IAsyncResult and takes in both an object array and an object for context. This fits the signature for a Typemock.DynamicReturnValue delegate, which we'll be using to mock out calls to SynchronizationManager. In this static method, we're going to pull out the parameters that were being passed to the SynchronizationManager, we're going to cast them to their appropriate types, and then we're going to actually execute the thing that we wanted the SynchronizationManager to execute.
private static IAsyncResult SynchronizationManagerBeginInvoke(object[] parameters, object context)
{
Delegate exec = parameters[0] as Delegate;
SynchronizationManagerMethodCall call = delegate(object[] callParams) { return exec.DynamicInvoke(callParams); };
object[] paramArray = parameters[1] as object[];
IAsyncResult result = call.BeginInvoke(paramArray, null, null);
result.AsyncWaitHandle.WaitOne();
return result;
}
You'll notice a little oddness there at the end - we're executing it asynchronously but we're immediately waiting on the result, basically rendering the call synchronous. This is because a call to SynchronizationManager.BeginInvoke returns an IAsyncResult and we need to return that same type when we mock calls to it… but we also don't want to have to wait in our unit tests for the calls to complete necessarily. If you have code that actually waits on the handle yourself, you can probably omit the WaitOne call. Doing it this way makes it pretty easy, though.
Finally, you'll need to use Typemock to set up your tests. In MSTest, that looks like this:
[TestInitialize]
public void TestInitialize()
{
DynamicReturnValue beginInvoke = new DynamicReturnValue(SynchronizationManagerBeginInvoke);
using (RecordExpectations recorder = RecorderManager.StartRecording())
{
IAsyncResult dummyResult = SynchronizationManager.BeginInvoke(null, null);
recorder.Return(beginInvoke);
recorder.RepeatAlways();
}
}
That's it - now every time something calls SynchronizationManager.BeginInvoke, it'll get routed through the static method you declared instead of going through the real SynchronizationManager. That means you can unit test code that calls SynchronizationManager without having to be in a Visual Studio runtime environment.
Hi Travis,
We're glad that you've found a way to overcome this limitation. Thank you for sharing your solution with us. We greatly appreciate this! Undoubtedly, it will be rather useful for other customers who are looking for this functionality.
Thanks,
Vito