2

I have a function that is kicked off by an event handler. In this function a new task is created and ran to set a global variable that I will need later, within another event handler.

There are a couple other functions between these two as well which don't change any of the variables used within these. But here is what it kind of looks like this.

private void EventWhereINeedTheGlobalVariableLater(object sender, Event e)
{
    //...work...

    need _variableIneed


    //....more work...
}


private void EventWhereISetGlobalVariable(object sender, Event e)
{
    //....work....
    //...make cancellationToken...
    //groups, isCommonalityGroup are other global variables already set. 

    Task.Factory.StartNew(() =>
    {
        // Clear variable to get new value
        _variableIneed = null;

        // Execute query to get new value
        _variableIneed = _workingManager.GetValue(groups, isCommonalityGroup, cancellationToken);

       RefreshView();

    }, cancellationToken);
}

I'm running into race conditions where the variable I need _variableIneed is null within the second event handler and it can't be. It works fine if I'm not flying through and trying to create enough events to crash the wpf program, but I need it work even if I do that.

Is there something I can do to get past these race conditions?

I've tried using the .ContinueWith with the option of OnlyOnRanToCompletion or whatever it is. Any other things I could try?

**Note I can't do a lot with changing how the events are ordered/handled/worked through. It's a pretty set in stone design and I just have to work around it and keep it more or less how it is.

**Update

I have also tried using the ParallelExtensionsExtras with the OrderedTaskScheduler class and I still end up getting a null reference on the variable I need.

B-M
  • 1,231
  • 1
  • 19
  • 41
  • There's nothing that you can do if the `EventWhereINeedTheGlobalVariableLater` handler is called before the variable is set in the `EventWhereISetGlobalVariable` handler. – Sheridan Sep 24 '14 at 14:10
  • You mean you get null in `EventWhereINeedTheGlobalVariableLater`? May be `Task` isn't completed? You need to synchronize it. – Sriram Sakthivel Sep 24 '14 at 14:11
  • Yes @SriramSakthivel I get a null value for the global variable I need. – B-M Sep 24 '14 at 14:13
  • Which means `EventWhereISetGlobalVariable` is not called or task created by `StartNew` has not been completed. You may need to call `task.Wait` or `await` or `ContinueWith` to synchronize the access.But still there can be races(Another task started and set `_variableIneed` to null) before you access it. – Sriram Sakthivel Sep 24 '14 at 14:16
  • @SriramSakthivel "there can be races(Another task started and set...) that's what is happening. Is there anything I can do to combat that? I'm trying to avoid using `task.wait` if at all possible. – B-M Sep 24 '14 at 14:20
  • Alexei Levenkov has a good answer [here](http://stackoverflow.com/a/26019357/2530848) – Sriram Sakthivel Sep 24 '14 at 14:26

2 Answers2

5

When you have a Task to generate a value don't set the result to a global variable, have that result be the Result of the task, and store that task. When some other code later on needs that result it can get it from the task. This will allow the Task class to handle all of the complex synchronization logic, prevent the result from being used before the task has actually computed it, etc.

Of course for the event that needs to use the result it'll presumably need to not block on that task, but execute the remainder of the code that needs the result asynchronously after the task completes. This can be done very easily by using await on that task. If you're only using .NET 4.0 then you can use ContinueWith explicitly instead.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • So what would that look like, to have the result be the `Result` of the task and then accessing it from the task later? – B-M Sep 24 '14 at 14:33
  • +1. Use 4.5 and `async` code, or at least chain Tasks. – Alexei Levenkov Sep 24 '14 at 14:33
  • @Brett The lambda for `StartNew` should `return` the result, rather than setting another variable. The event handler should then `await` the `Task` that you start. – Servy Sep 24 '14 at 14:35
  • Sorry to throw a wrench in here (potentially) but if there is another function call within the `startnew` lambda, this won't. Will it? I just added the other function call to the question (I left it out before, my bad). – B-M Sep 24 '14 at 14:41
  • @Brett It doesn't matter what it needs to do to compute its result. All that's relevant here is how it exposes the result externally. – Servy Sep 24 '14 at 14:47
0

Use Servy's approach - async/Tasks. This answer is strictly for entertainment purposes or if you can't use .Net 4.0+ or 3.5 with Rx.


Since you can't change order of events you either need to expect the variable to be null from time to time or prevent it from ever being seen as null.

One option would be to poll this variable and only do work when it is not set to null (may need timer if your EventWhereINeedTheGlobalVariableLater does not fire repeatedly).

Alternatively you can just always keep value in the variable OR prevent other threads from seeing null value.

Preventing null from being visible in success case, still may be null if "long computation" fails:

   object lockObj = new object(); // at class level

   private void EventWhereISetGlobalVariable ...
   {
     lock(lockObj)
     {
       _variableIneed = null;
       // some long and convoluted computations
       _variableIneed = someResult;
     }
   }

   private void EventWhereINeedTheGlobalVariableLater(object sender, Event e)
   {
     lock(lockObj)
     {
      // unsing _variableIneed 
     }
   }

Preventing it to be set to null by only setting value when we have one (either locking around access to variable or volatile would work, prefer locking as in other sample). This is common pattern for caching some value that takes long time to compute and it is ok if users of the variable see slightly stale value.

   volatile WhateverYourType _variableIneed;
   private void EventWhereISetGlobalVariable ...
   {
       // some long and convoluted computations
       _variableIneed = someResult ?? _variableIneed;
   }

Note:

  • make sure you understand how you deals with any locking / synchronization in your code - so be very careful using that variable in more than one place. Consider to to wrap access to the variable with lock.
  • consider copying value to local variable inside lock and using local variable in code that "need value". Otherwise other thread may change it to new value/null.
  • for one-time set variables consider Lazy<T> class that deals with such initialization or if use of Cache to store value is more appropriate.
Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • I have a question - does it make sence to make a lock for a variable, that will be used in parallel? I mean if from another thread I would like to get or set this variable, it will be locked – Sasha Sep 24 '14 at 14:27
  • The TPL is designed specifically to avoid requiring you to do things like this, due to the complexity they add to the code, the potential for errors, etc. This is not a productive approach for solving this problem, given that he's already using the TPL. – Servy Sep 24 '14 at 14:29
  • @Sasha lock prevents more than one thread to run code wrapped with the same lock - so other threads will wait till one and only tread leaves the block wrapped with `lock`. This will guarantee that value can't be changed by other threads while current one using/setting/computing it. Consider reading [lock](http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx) and [Critical section](http://en.wikipedia.org/wiki/Critical_section). – Alexei Levenkov Sep 24 '14 at 14:31