Accessing values in Task.ContinueWith

2019-09-08 15:51发布

I am starting a task with the following code:

var token = tokenSource.Token;
var taskWithToken = new Task(() =>
        new ProcessMyCommand(_unitOfWork, ..., batchRunId, token).Execute(), 
        token);

In my continue with, I need to know the batchRunId and possibly some other variables listed in the ..., however, it doesn't apepar that this is possible???

taskWithToken.ContinueWith(task =>
        {
            if (!task.IsCanceled)
                return;

            //TODO: make sure no more subsequent runs happen

            //TODO: sync with source data
        }
    );

Is there something I am missing? How can I make sure the .ContinueWith executes with access to the values it needs?

2条回答
聊天终结者
2楼-- · 2019-09-08 16:28

You can create your MyTaskData class to store your data and result and it may as well store MyTaskData PreviousTaskData property (from previous task) creating linked list of results. Create a Task<MyTaskData> inside which, at the end, you return myNewTaskData;. Then ContinueWith<MyTaskData>(...) inside which you can get previous results through Task.Result property. As for continuation on cancelled Task ContinueWith has a variant with TaskContinuationOptions parameter (MSDN) where you can specify NotOnCanceled

查看更多
Melony?
3楼-- · 2019-09-08 16:33

First, I'm not even sure if you need continuation in your case. Your code could be simplified into something like:

var taskWithToken = new Task(() =>
    {
        new ProcessMyCommand(_unitOfWork, ..., batchRunId, token).Execute();

        // code from the continuation here
    },
    token);

But if you do want to use ContinueWith() and you're worried about using it because of the ReSharper warning, then you don't have to. Most of the time, code like this is perfectly fine and you can ignore the warning.

Longer version: when you write a lambda that references something from the enclosing scope (so called closure), the compiler has to generate code for that. How exactly does it do that is an implementation detail, but the current compiler generates a single closure class for all closures inside a single method.

What this means in your case is that the compiler generates a class that contains the locals this (because of _unitOfWork), request and batchRunId (and maybe others that you didn't show). This closure object is shared between the new Task lambda and the ContinueWith() lambda, even though the second lambda doesn't use request or this. And as long as the second lambda is referenced from somewhere, those objects can't be garbage collected, even though they can't be accessed from it.

So, this situation can lead to a memory leak, which I believe is why ReSharper is warning you about it. But in almost all cases, this memory leak either doesn't exist (because the second lambda isn't referenced longer than the first one) or it's very small. So, most of the time, you can safely ignore that warning. But if you get mysterious memory leaks, you should investigate the way you're using lambdas and especially places where you get this warning.

查看更多
登录 后发表回答