C# 如何重试先行异步任务

C# 如何重试先行异步任务,c#,asynchronous,error-handling,continuations,retry-logic,C#,Asynchronous,Error Handling,Continuations,Retry Logic,我需要进行一系列异步调用,其中一些调用依赖于在启动其他任务之前完成的其他任务。我希望对这些调用具有重试逻辑,因为它们都是对外部资源的调用。这是我们的原型代码,没有任何异常处理或重试逻辑:(这可以完成任务,但没有任何弹性或错误处理)。经过相当多的研究,我需要一些帮助,以一种弹性的方式将其组合在一起 Task uploadHdr = null; Task uploadElig = null; Task ImportHdrCPA = null; Task ImportHdrCCRS = null; T

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

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;
            }
        }
    }
}

下面是我在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;
            }
        }
    }
}


你试过什么?您看到的错误是什么?目前这似乎是一个基于观点的问题——你应该避开哪个?你看过了吗?我看过Polly,但很难让它正常工作。Polly将很好地处理重试部分,但似乎不允许我使用ContinueWith来处理先行项。我知道必须有一种比我使用的循环检查方法更好的方法。马克·戴维斯:我从下面的文章中尝试了几件事,但一直遇到一个无法解决的对象引用未设置错误:更新:我添加了以下行:var retryPolicy=Policy.Handle().WaitAndRetryAsync(3,I=>TimeSpan.FromSeconds(10));在代码顶部,将以uploadHdr=开头的行更改为uploadHdr=retryPolicy.ExecuteAsync(async()=>{await hdrBlob.UploadFromStreamAsync(fsHdr);});这会导致在void Main(字符串[]args)中代码的最后一行出现object ref not set错误,并且没有指示实际失败的内容。该错误在命中任务时抛出。具体是哪一行。您尝试了什么?您看到的错误是什么?目前这似乎是一个基于观点的问题——你应该避开哪个?你看过了吗?我看过Polly,但很难让它正常工作。Polly将很好地处理重试部分,但似乎不允许我使用ContinueWith来处理先行项。我知道必须有一种比我使用的循环检查方法更好的方法。马克·戴维斯:我从下面的文章中尝试了几件事,但一直遇到一个无法解决的对象引用未设置错误:更新:我添加了以下行:var retryPolicy=Policy.Handle().WaitAndRetryAsync(3,I=>TimeSpan.FromSeconds(10));在代码顶部,将以uploadHdr=开头的行更改为uploadHdr=retryPolicy.ExecuteAsync(async()=>{await hdrBlob.UploadFromStreamAsync(fsHdr);});这会在void Main(string[]args)中代码的最后一行出现object ref not set错误,没有任何实际失败的指示。该错误在到达任务时抛出。具体是哪一行。我尝试了这一点,但做了一些轻微的修改(我不想等待调用以便启动其他任务),并在对象引用未设置错误处结束。uploadHdr=RetryableAction(async()=>{await hdrBlob.UploadFromStreamAsync(fsHdr);},retryCounter);您必须进行调试,并查看什么是您不希望的空值。可能
hdrBlob
?Gabriel-它显示了在void Main的最后一行(实际的闭合支架)上发生的错误,并且没有给出失败的其他指示。RetryAbleAction调用中行上的断点显示hdrBlob已设置且有效。如果这是控制台应用程序,请将
Main
方法声明更改为
public static async Task Main(string[]args)
并在
RetryAbleAction
前面使用
wait
。否则,您的应用程序实际上不会等待它完成。当它到达任务时会抛出错误。具体来说,是哪一行。我尝试了这一点,但做了一些轻微的修改(我不想等待调用以便启动其他任务),并以对象引用未设置错误结束。uploadHdr=RetryableAction(async()=>{await hdrBlob.UploadFromStreamAsync(fsHdr);},retryCounter);您必须进行调试,并查看什么是您不希望的空值。可能
hdrBlob
?Gabriel-它显示了在void Main的最后一行(实际的闭合支架)上发生的错误,并且没有给出失败的其他指示。RetryAbleAction调用中行上的断点显示hdrBlob已设置且有效