1
private Data GetDefaultData()
{
    var data = Task.Factory.StartNew(() => GetData());
    return data.Result;
}

If GetData() executes in 100ms and I run one GetDefaultData() per 10ms. Is it correct that first 10 calls will use the same data.Result? GetData() collect Data inside lock statement. If not how to change the code to provide this opportunity?

  • Given the code provided, a different task will be spun up for each call to `GetDefaultData()`. Whether the value of `data.Result` is the same between the calls depends on what happens inside `GetData` – M.Babcock Dec 23 '15 at 16:19
  • GetData() collect Data inside lock statement. –  Dec 23 '15 at 16:21
  • Do you want to call once and read many? Or do you want the data to refresh after X milliseconds? – Yuval Itzchakov Dec 23 '15 at 16:40
  • Let's say, we have the first call `GetDefaultData` (`GetData()` executes in 100ms), and then we have 10 calls(`GetDefaultData()` per 10ms). I want that this rest of calls will get the same answer as the first one. –  Dec 23 '15 at 16:50
  • You can take a look at [this .Net Fiddle](https://dotnetfiddle.net/zTVKfx) as an example for multiple subscribers waiting on one `Task`. That shows that, if the `GetData` call is already underway, then wait for the actively loading results. Otherwise, kick off a new call (which others then would wait on). Are you after something like that, or just to load once and then that would always be the result (which is a much simpler problem)? – Mike Guthrie Dec 23 '15 at 18:02

3 Answers3

2

Let's say, we have the first call GetDefaultData (GetData() executes in 100ms), and then we have 10 calls(GetDefaultData() per 10ms). I want that this rest of calls will get the same answer as the first one.

It sounds like you want the Lazy<T> class.

public class YourClass
{
    private readonly Lazy<Data> _lazyData;

    public YourClass()
    {
        _lazyData = new Lazy<Data>(() => GetData());
    }

    private Data GetDefaultData()
    {
       return _lazyData.Value;
    }

    public Data GetData()
    {
        //...
    }
}

The first thread to call GetDefaultData() will run GetData() when it hits _lazyData.Value, all the rest of the threads will block on the call _lazyData.Value till the first thread finishes and use the result from that first thread's call. GetData() will only ever be called once.

If you don't want the call to block you can easily make a AsyncLazy<T> class that uses Threads internally.

public class AsyncLazy<T> : Lazy<Task<T>>
{
    public AsyncLazy(Func<T> valueFactory) :
        base(() => Task.Run(valueFactory))
    { 
    }

    public AsyncLazy(Func<Task<T>> taskFactory, bool runFactoryInNewTask = true) :
        base(() => runFactoryInNewTask ? Task.Run(taskFactory) : taskFactory())
    { 
    }

    //This lets you use `await _lazyData` instead of doing `await _lazyData.Value`
    public TaskAwaiter<T> GetAwaiter() 
    { 
        return Value.GetAwaiter(); 
    }
}

Then your code becomes (I also made GetData an async function too, but the overloads of AsyncLazy let it be either or)

public class YourClass
{
    private readonly AsyncLazy<Data> _lazyData;

    public YourClass()
    {
        _lazyData = new AsyncLazy<Data>(() => GetData(), false);
    }

    private async Task<Data> GetDefaultData()
    {
       //I await here to defer any exceptions till the returned task is awaited.
       return await _lazyData;
    }

    public Task<Data> GetData()
    {
        //...
    }
}

EDIT: There are some possible issues with AsyncLazy, see here.

Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 1
    Two things are important to note. First is the fact that this implementation of `AsyncLazy` won't propogate exceptions in case such occurs, and the IsValueCreated will be set to `false`. Furthermore, `Lazy` is problematic in regards to exceptions in general. It will cache and rethrow them in case the factory method throws. This can be problematic if this lazy is doing network IO. – Yuval Itzchakov Dec 23 '15 at 18:27
  • More on the first problem can be [found here](http://stackoverflow.com/questions/33872588/caching-the-result-from-a-n-async-factory-method-iff-it-doesnt-throw/33874601#33874601) – Yuval Itzchakov Dec 23 '15 at 18:29
  • 1
    @YuvalItzchakov Thanks for the info! I updated the answer to a link to the other question. – Scott Chamberlain Dec 23 '15 at 19:13
0

In short: No.

Each time you call GetDefaultData() a new task is started, so Data.Result will remain unchanged for the duration of GetData() and then contain what you assigned to it in GetData(). Also returning a value from a new Task object will do you no good - this is how multitasking works. Your code will continue to execute in the main thread, but you result value will only be set once the separate task is finished executing. Whether it contains lock statements or not.

srandppl
  • 571
  • 4
  • 14
  • What do I need to change for caching? scenario that I describe in the question –  Dec 23 '15 at 16:28
  • That depends on what GetData actually does. Why do you need Tasks in the first place? What are you doing with the result= – srandppl Dec 23 '15 at 16:32
  • I need to handle concurrent request by waiting the result of an already running operation(my own cache). –  Dec 23 '15 at 16:34
  • `GetData` just collect information and returns `Data` object –  Dec 23 '15 at 16:36
  • What you could do, is setup one WorkerThread that periodically updates your result. This way you will always have the last result accessible (with a lock while the workerThread writes the update). Is taht what you want? – srandppl Dec 23 '15 at 16:36
  • Let's say, we have the first call GetDefaultData (GetData() executes in 100ms), and then we have 10 calls(GetDefaultData() per 10ms). I want that this rest of calls will get the same answer as the first one. –  Dec 23 '15 at 16:50
0

Probably the ReaderWriterLock will suit you in this purpose. ReaderWriterLock is used to synchronize access to a resource. At any given time, it allows either concurrent read access for multiple threads, or write access for a single thread. This logic should be embaded into your GetData method probably, so that depening on some timeout it could either use writelock and hold it for this timeout time, otherwise use read operation.

Alex
  • 8,827
  • 3
  • 42
  • 58