Ticket Q204445
Visible to All Users

How Do You Test Code Using SynchronizationManager?

created 16 years ago

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:

  1. Is SynchronizationManager still the best way to do this sort of thread-safe logging or is there a better way?
  2. If SynchronizationManager is still good, how can I get it to run in a unit test environment?
Show previous comments (2)
DevExpress Support Team 16 years ago

    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.

      DevExpress Support Team 16 years ago

        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

        Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

        Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.