c# - 如何重试先前的异步任务

标签 c# asynchronous error-handling continuations retry-logic

在开始其他任务之前,我需要进行一系列异步调用,其中一些依赖于其他任务完成。我想为这些调用重试逻辑,因为它们都是对外部资源的调用。这是我们的原型(prototype)代码,没有任何异常处理或重试逻辑:(这可以完成工作,但没有任何弹性或错误处理)。经过相当多的研究,我需要一些帮助以一种有弹性的方式将它们组合在一起。

Task uploadHdr = null;
Task uploadElig = null;
Task ImportHdrCPA = null;
Task ImportHdrCCRS = null;
Task ImportEligCPA = null;
Task ImportEligCCRS = null;
Task ProcessCPA = null;
Task ProcessCCRS = null;

//Connect to blob storage
CloudStorageAccount storageacc = CloudStorageAccount.Parse("connection string to blob storage");
CloudBlobClient blobClient = storageacc.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("ourcontainer");           

//Import Header to Choice PA
using (SqlConnection conCPA = new SqlConnection("connection string for CPA"))
using (SqlConnection conCCRS = new SqlConnection("connection string for CCRS"))
{
    conCPA.Open();
    conCCRS.Open();

    //Upload HDR file
    CloudBlockBlob hdrBlob = container.GetBlockBlobReference("Header.txt");
    FileStream fsHdr = System.IO.File.OpenRead(@"C:\Development\Header.txt");
    uploadHdr = hdrBlob.UploadFromStreamAsync(fsHdr);
    allTasks.Add(uploadHdr);
    Console.WriteLine("Started Header Upload " + stopwatch.Elapsed.ToString());

    //Upload eligibility segment file
    CloudBlockBlob elgBlob = container.GetBlockBlobReference("Detail.txt");
    FileStream fsElig = System.IO.File.OpenRead(@"C:\Development\Detail.txt");
    uploadElig = elgBlob.UploadFromStreamAsync(fsElig);
    allTasks.Add(uploadElig);
    Console.WriteLine("Started Detail Upload " + stopwatch.Elapsed.ToString());

    while (allTasks.Any())
    {
        Task.WhenAny(allTasks.ToArray());

        if (uploadHdr.IsCompletedSuccessfully && ImportHdrCPA == null)
        {
            SqlCommand cmdHdrCPA = new SqlCommand("dbo.spImportHeader", conCPA) { CommandType = CommandType.StoredProcedure, CommandTimeout = 0 };
            cmdHdrCPA.Parameters.Add("@FileName", SqlDbType.VarChar);
            cmdHdrCPA.Parameters["@FileName"].Value = "Header.txt";
            ImportHdrCPA = cmdHdrCPA.ExecuteNonQueryAsync();
            allTasks.Add(ImportHdrCPA);
            Console.WriteLine("Started Header Import 1 " + stopwatch.Elapsed.ToString());
        }

        if (ImportHdrCPA.IsCompletedSuccessfully && ImportHdrCCRS == null)
        {
            SqlCommand cmd = new SqlCommand("dbo.spNCEligibilityImportHeader", conCCRS) { CommandType = CommandType.StoredProcedure, CommandTimeout = 0 };
            cmd.Parameters.Add("@FileName", SqlDbType.VarChar);
            cmd.Parameters["@FileName"].Value = "CSC_NCEligibility_Hdr_i_20191105.txt";
            ImportHdrCCRS = cmd.ExecuteNonQueryAsync();
            allTasks.Add(ImportHdrCCRS);
            Console.WriteLine("Started Header Import 2 " + stopwatch.Elapsed.ToString());
        }

        if(uploadElig.IsCompletedSuccessfully && ImportEligCPA == null)
        {
            SqlCommand cmd = new SqlCommand("dbo.spImportDetail", conCPA) { CommandType = CommandType.StoredProcedure, CommandTimeout = 0 };
            cmd.Parameters.Add("@FileName", SqlDbType.VarChar);
            cmd.Parameters["@FileName"].Value = "Detail.txt";
            ImportEligCPA = cmd.ExecuteNonQueryAsync();
            allTasks.Add(ImportEligCPA);
            Console.WriteLine("Started Detail Import 1 " + stopwatch.Elapsed.ToString());
        }

        if(ImportEligCPA != null && ImportEligCPA.IsCompletedSuccessfully && ImportEligCCRS == null)
        {
            SqlCommand cmd = new SqlCommand("dbo.spImportDetail", conCCRS) { CommandType = CommandType.StoredProcedure, CommandTimeout = 0 };
            cmd.Parameters.Add("@FileName", SqlDbType.VarChar);
            cmd.Parameters["@FileName"].Value = "Detail.txt";
            ImportEligCCRS = cmd.ExecuteNonQueryAsync();
            allTasks.Add(ImportEligCCRS);
            Console.WriteLine("Started Detail Import 2 " + stopwatch.Elapsed.ToString());
        }

        if (ImportHdrCPA.IsCompletedSuccessfully && ImportEligCPA != null && ImportEligCPA.IsCompletedSuccessfully && ProcessCPA == null)
        {
            SqlCommand cmd = new SqlCommand("dbo.spProcess", conCPA) { CommandType = CommandType.StoredProcedure, CommandTimeout = 0 };
            ProcessCPA = cmd.ExecuteNonQueryAsync();
            allTasks.Add(ProcessCPA);
            Console.WriteLine("Started Processing 1 " + stopwatch.Elapsed.ToString());
        }

        if (ImportHdrCCRS != null && ImportHdrCCRS.IsCompletedSuccessfully && ImportEligCCRS != null && ImportEligCCRS.IsCompletedSuccessfully && ProcessCCRS == null)
        {
            SqlCommand cmd = new SqlCommand("dbo.spProcess", conCCRS) { CommandType = CommandType.StoredProcedure, CommandTimeout = 0 };
            ProcessCCRS = cmd.ExecuteNonQueryAsync();
            allTasks.Add(ProcessCCRS);
            Console.WriteLine("Started Processing 2 " + stopwatch.Elapsed.ToString());
        }                  

        if (ProcessCCRS != null && ProcessCPA != null)
        {
            Task.WaitAll(allTasks.ToArray());
            allTasks.Clear();
        }

        Thread.Sleep(5000);
    }

最佳答案

这是我在 WinForms 应用程序中使用的代码,用于为用户提供重试或取消 I/O 请求的选项。如果在您提供的任何代码中引发异常,那么它将弹出一个带有异常消息的对话框,并询问用户是否要重试或取消。如果他们选择重试,它将重试。如果他们选择取消,它将重新抛出异常。

如果您不使用 WinForms,您可以修改向用户发出请求的方式 - 或者只需重试特定次数而无需用户干预。

即使您使用的是 WinForms,您也可能需要对代码进行一些修改。我把它放在表单的代码中,因此调用 Invoke .

这有两个版本:一个用于同步,一个用于异步。

/// <summary>
/// Gives the option to retry if the provided action throws an exception
/// </summary>
/// <param name="action">The action to perform</param>
public void RetryableAction(Action action) {
    while (true) {
        try {
            action();
            break;
        } catch (Exception e) {
            var dr = DialogResult.Retry;
            Invoke((MethodInvoker) (() => dr = MessageBox.Show(this,
                $"There was an error:\r\n{e.Message}\r\n\r\nDo you want to retry?", "Error",
                MessageBoxButtons.RetryCancel, MessageBoxIcon.Error)));

            if (dr == DialogResult.Cancel) {
                throw;
            }
        }
    }
}

public async Task RetryableAction(Func<Task> action) {
    while (true) {
        try {
            await action();
            break;
        } catch (Exception e) {
            var dr = DialogResult.Retry;
            Invoke((MethodInvoker)(() => dr = MessageBox.Show(this,
                $"There was an error:\r\n{e.Message}\r\n\r\nDo you want to retry?", "Error",
                MessageBoxButtons.RetryCancel, MessageBoxIcon.Error)));

            if (dr == DialogResult.Cancel) {
                throw;
            }
        }
    }
}

这是一个如何使用它的示例(在异步版本的情况下):
await RetryableAction(async () => {
    uploadHdr = await hdrBlob.UploadFromStreamAsync(fsHdr);
});

关于c# - 如何重试先前的异步任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59203664/

相关文章:

jsf - 将堆栈跟踪添加到部分响应

c# - 如何检查文件的IndexOutOfRangeException并将其记录?

c# - 一次只允许每个用户登录一次

c# - 如何使用 Dapper 执行插入并返回插入的标识?

python - 如何调试具有自定义异常处理程序的 Flask 应用程序?

node.js - 根据数组的大小进行多个 api 调用

javascript - Request.Get 和异步/等待

c# - 无法使用 EPPlus 删除工作表

c# - WPF 中的下拉文本区域

c# - 如何返回 IAsyncOperation<TReturn> 结果?