How to cancel Task C#

2020-03-26 19:26发布

I have a Task which retrieves some data from a Web Service. The Webservice should only get called when I open a page (this is Xamarin.Forms) and it should only run if there isn't another version of the task running - This part works fine.

When I move away from the page, I want to cancel the Task - I can't seem to get that piece working. When OnAppearing(), OnDisappearing(), OnAppearing() is hit before the webservice returns the data, logs just write "Task has attempted to start..." which means that the Task didn't get cancelled. Instead the task should have been cancelled whenOnDisappearing()` was called.

I have tried to follow some of the examples posted on StackOverflow but doesn't seem to work and I can't quite figure it out.

The code I have is below:

OnAppearing:

private Task task;
CancellationTokenSource tokenSource = new CancellationTokenSource();

protected override void OnAppearing()
{
    base.OnAppearing();

    if (task != null && (task.Status == TaskStatus.Running || task.Status == TaskStatus.WaitingToRun || task.Status == TaskStatus.WaitingForActivation))
    {
        Debug.WriteLine("Task has attempted to start while already running");
    }
    else
    {
        Debug.WriteLine("Task has began");

        var token = tokenSource.Token;
        task = Task.Run(async () =>
        {

            await GetDataAsync();
            /* EDIT: This is what I originally posted, but doesn't work so have commented it out
            while (!token.IsCancellationRequested)
            {
                await GetDataAsync();
            }
             */
       }, token);
   }      
}

OnDisappearing:

protected override void OnDisappearing()
{
    base.OnDisappearing();
    Debug.WriteLine("Page dissapear");
    tokenSource.Cancel();
    Debug.WriteLine("Task Cancelled");
}

GetData Method

public async Task GetData()
{
    WebAPI api = new WebAPI();

    try
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            PageLoading();
        });

        string r = await api.GetProfileStatus(token);
        Device.BeginInvokeOnMainThread(async () =>
        {
           if (r == "OK")
           {
                GetDataSuccess();
           }
        }
     }
     catch (Exception e)
     {
        // log exception
     }
}

1条回答
三岁会撩人
2楼-- · 2020-03-26 19:56

You're doing the right check in a wrong place:

var token = tokenSource.Token;
task = Task.Run(async () =>
{
    while (!token.IsCancellationRequested)
    {
        await GetDataAsync();
    }
}

Here you start the task, and after that start it again and again in a loop. The right thing to do is check the token inside your GetData, and futher to api variable, if possible. In this case you'll be able to cancel the service call as you need.

Side notes:

  • make your event handlers async, and do not wrap your work in Task.Run
  • make your GetData Task<T> so you can actually return status to the calling code, then you can remove BeginInvokeOnMainThread, as await will restore the context after async operation

So your code will be something like this:

protected override async void OnAppearing()
{
    base.OnAppearing();

    if (task != null && (task.Status == TaskStatus.Running || task.Status == TaskStatus.WaitingToRun || task.Status == TaskStatus.WaitingForActivation))
    {
        Debug.WriteLine("Task has attempted to start while already running");
    }
    else
    {
        Debug.WriteLine("Task has began");

        var token = tokenSource.Token;
        PageLoading();
        var r = await GetDataAsync(token);
        if (r == "OK")
        {
            GetDataSuccess();
        }
   }      
}

public async Task GetDataAsync(CancellationToken token)
{
    WebAPI api = new WebAPI();
    if (token.IsCancellationRequested)
    {
        token.ThrowIfCancellationRequested();
    }

    try
    {
        return await api.GetProfileStatus(token);
    }
    catch (Exception e)
    {
        // log exception and return error
        return "Error";
    }
}
查看更多
登录 后发表回答