首页 > 解决方案 > 如何在给定时间后取消异步任务以及如何重新启动失败的任务?

问题描述

当异步任务需要很长时间才能完成或者它可能永远不会完成时,如何取消它?是否可以为每个任务使用给定的时间(例如 10 秒),如果在给定的时间内没有完成,那么任务将自动取消?

失败后是否可以重新启动任务或再次创建相同的任务?如果任务列表中的一项任务失败,我该怎么办?是否可以只重新启动失败的任务?

在我的代码中, playerCountryDataUpdate 应仅在 TasksList1 中的每个任务完成且没有错误或异常的情况下执行。我想在任务失败时重新启动它。当相同的任务再次失败时,不要重新启动它并在屏幕上显示错误消息。我怎样才能做到这一点?

bool AllMethods1Completed = false;
bool AllMethods2Completed = false;

public async Task PlayerAccountDetails()
{
    var playerCountryDataGet = GetPlayerCountryData();
    var playerTagsData = GetPlayerTagsData();
    var TasksList1 = new List<Task> { playerCountryDataGet, playerTagsData };

    try
    {
        await Task.WhenAll(TasksList1);
        AllMethods1Completed = true;
    }
    catch
    {
        AllMethods1Completed = false;
    }

    if (AllMethods1Completed == true)
    {
        var playerCountryDataUpdate = UpdatePlayerCountryData("Germany", "Berlin");
        var TasksList2 = new List<Task> { playerCountryDataUpdate };

        try
        {
            await Task.WhenAll(TasksList2);
            AllMethods2Completed = true;
        }
        catch
        {
            AllMethods2Completed = false;
        }
    }
}

private async Task GetPlayerTagsData()
{
    var resultprofile = await PlayFabServerAPI.GetPlayerTagsAsync(new PlayFab.ServerModels.GetPlayerTagsRequest()
    {
        PlayFabId = PlayerPlayFabID
    });

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
    {
        if ((resultprofile.Result != null) && (resultprofile.Result.Tags.Count() > 0))
            PlayerTag = resultprofile.Result.Tags[0].ToString();
    }
}

private async Task GetPlayerCountryData()
{
    var resultprofile = await PlayFabClientAPI.GetUserDataAsync(new PlayFab.ClientModels.GetUserDataRequest()
    {
        PlayFabId = PlayerPlayFabID,
        Keys = null
    });

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
    {
        if (resultprofile.Result.Data == null || !resultprofile.Result.Data.ContainsKey("Country") || !resultprofile.Result.Data.ContainsKey("City"))
            Console.WriteLine("No Country/City");
        else
        {
            PlayerCountry = resultprofile.Result.Data["Country"].Value;
            PlayerCity = resultprofile.Result.Data["City"].Value;
        }
    }
}

private async Task UpdatePlayerCountryData(string country, string city)
{
    var resultprofile = await PlayFabClientAPI.UpdateUserDataAsync(new PlayFab.ClientModels.UpdateUserDataRequest()
    {
        Data = new Dictionary<string, string>() {
            {"Country", country},
            {"City", city}
            },
        Permission = PlayFab.ClientModels.UserDataPermission.Public
    });

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
        Console.WriteLine("Successfully updated user data");
}

标签: c#

解决方案


您需要直接在任务本身中构建取消机制。C# 提供了一个 CancellationTokenSource 和 CancellationToken 类来帮助解决这个问题。https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netcore-3.1

将(可选)CancellationToken 添加到任务的参数中。然后以适当的时间间隔检查令牌以确定任务是否需要在完成之前中止。

在长时间运行的查询的情况下,最好弄清楚如何将查询分成块,然后检查查询之间的 CancellationToken。

private async Task GetPlayerXXXData(CancellationToken ct = null) {
   int limit = 100;
   int total = Server.GetPlayerXXXCount();
   List<PlayerXXXData> results = new List<PlayerXXXData>();
   while((ct == null || ct.IsCancellationRequested) && result.Count < total) {
      result.AddRange(Server.GetPlayerXXXData(result.Count, limit));
   }
   return results;
}

注意上面没有错误处理;但你明白了。您可以考虑通过使用您自己的自定义 IEnumerable 实现来实现延迟执行来使其更快(开始使用数据)。然后您可以查询一个块并在查询下一个块之前迭代该块。这也有助于防止您将过多的内容加载到 RAM 中 - 取决于您打算处理的记录数量。


推荐阅读