简体   繁体   中英

How to return the result from Task?

I have the following methods:

public int getData() { return 2; } // suppose it is slow and takes 20 sec

// pseudocode
public int GetPreviousData()
{
    Task<int> t = new Task<int>(() => getData());
    return _cachedData; // some previous value
    _cachedData = t.Result; // _cachedData == 2
}

I don't want to wait for the result of an already running operation.

I want to return _cachedData and update it after the Task will finish.

How to do this? I'm using .net framework 4.5.2

You might want to use an out parameter here:

public Task<int> GetPreviousDataAsync(out int cachedData)
{
    Task<int> t = Task.Run(() => getData());
    cachedData = _cachedData; // some previous value
    return t; // _cachedData == 2
}

int cachedData;
cachedData = await GetPreviousDataAsync(out int cachedData);

Pay attention to the Task.Run thing: this starts a task using the thread pool and returns a Task<int> to let the caller decide if it should be awaited, continued or fire and forget it.

See the following sample. I've re-arranged everything into a class:

class A
{
    private int _cachedData;
    private readonly static AutoResetEvent _getDataResetEvent = new AutoResetEvent(true);

    private int GetData()
    {
        return 1;
    }

    public Task<int> GetPreviousDataAsync(out int cachedData)
    {
        // This will force calls to this method to be executed one by one, avoiding
        // N calls to his method update _cachedData class field in an unpredictable way
        // It will try to get a lock in 6 seconds. If it goes beyong 6 seconds it means that 
        // the task is taking too much time. This will prevent a deadlock
        if (!_getDataResetEvent.WaitOne(TimeSpan.FromSeconds(6)))
        {
            throw new InvalidOperationException("Some previous operation is taking too much time");
        }

        // It has acquired an exclusive lock since WaitOne returned true

        Task<int> getDataTask = Task.Run(() => GetData());
        cachedData = _cachedData; // some previous value

        // Once the getDataTask has finished, this will set the 
        // _cachedData class field. Since it's an asynchronous 
        // continuation, the return statement will be hit before the
        // task ends, letting the caller await for the asynchronous
        // operation, while the method was able to output 
        // previous _cachedData using the "out" parameter.
        getDataTask.ContinueWith
        (
            t =>
            {
                if (t.IsCompleted)
                    _cachedData = t.Result;

                // Open the door again to let other calls proceed
                _getDataResetEvent.Set();
            }
        );

        return getDataTask;
    }

    public void DoStuff()
    {
        int previousCachedData;
        // Don't await it, when the underlying task ends, sets
        // _cachedData already. This is like saying "fire and forget it"
        GetPreviousDataAsync(out previousCachedData);
    }
}

You need to store the cached value in your class, and then when the value is queried check whether you're already updating it. If you are just return the cached value. If not then launch a new query and return the cached value.

If you don't have to wait at all for the task to finish, you can have the function start the task and handle the setting in a ContinueWith

    public int GetPreviousData()
    {
        Task.Run((Func<int>)getData).ContinueWith(t => _cachedData = t.Result);            
        return _cachedData; // some previous value
    }

If race conditions are an issue, you can assign _cachedData to a variable first, then run the task and immediately return the variable, but if getData takes any time at all, that shouldn't be an issue.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM