c# - 如何处理异步运行方法中的异常?

标签 c# mysql task background-process

我正在构建一个程序,用于跟踪目录并在目录中创建新文件时向 mysql 数据库添加一个条目。不久前我发布了一个question了解如何让 FileSystemWatcher 类独立运行,同时填充要处理的后台队列,同时 FileSystemWatcher 继续搜索。我找到了解决方案,但想问一个后续问题。首先,这是代码:

这是观察者的构造函数

FileSystemWatcher watcher = new FileSystemWatcher
{
    Path = directoryToWatch,
    IncludeSubdirectories = true,
    NotifyFilter = NotifyFilters.Attributes |
                   NotifyFilters.DirectoryName |
                   NotifyFilters.FileName,
    EnableRaisingEvents = true,
    Filter = "*.*"
};

watcher.Created += (OnDirectoryChange);

这是 OnDirectoryChange 方法:

public void OnDirectoryChange(object sender, FileSystemEventArgs e)
{
    try
    {
        bq.QueueTask(() => storage.Insert(Settings.Default.added_files, e.Name));
    }
    catch (StorageException se)
    {
        Console.WriteLine(se.Message);
        HandleException(se, se.Filename, se.Number);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Catch all exceptions");
    }
}

HandleException 方法:

public void HandleException(Exception exceptionToHandle, string fileName = "empty", int number = 0)
{
    try
    {
        if (number < Acceptedammountofexceptions)
        {
            AddExceptionToStack(exceptionToHandle, fileName);
            RetryFailedFiles(new KeyValuePair<string, int>(fileName, number));
        }
        else if (number >= Acceptedammountofexceptions)
        {
            AddExceptionToCriticalList(exceptionToHandle, fileName);
            Console.WriteLine("Sorry, couldnt insert this file. See the log for more information.");
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("Exception in HandleException");
        throw;
    }
}

RetryFailedFiles 方法:

public void RetryFailedFiles(KeyValuePair<string, int> file)
{
    int count = file.Value + 1;
    try
    {
        bq.QueueTask(() => storage.Insert(Settings.Default.added_files, file.Key));
    }
    catch (StorageException se)
    {
        HandleException(se, file.Key, count);
        Console.WriteLine(se);
    }
}

如您所见,我想重试插入失败的文件一定次数。但是,此时不会重试失败的文件。说实话,我对自己的计划感到有点迷失。如果有帮助,这里是插入语句的代码。

public Task Insert(string tablename, string filename, int number=0)
{
    try
    {
        string query = "INSERT INTO " + tablename + " (FILE_NAME, " +
                       "Id) VALUES('" + filename + "', '" + Id + "')";

        using (MySqlConnection insertConn = new MySqlConnection(connectionString))
        {
            insertConn.Open();
            if (insertConn.State == ConnectionState.Open)
            {
                MySqlCommand insertCmd = new MySqlCommand(query, insertConn) {CommandType = CommandType.Text};
                while (insertCmd.Connection.State != ConnectionState.Open)
                {
                    // Spinlock
                }

                insertCmd.ExecuteNonQuery();
                insertConn.Close();
                return Task.CompletedTask;
            }
            StorageException se = new StorageException("Couldn't connect to database!", filename, number);
            Console.WriteLine(se.Message);
            return Task.FromException(se);
        }

    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        if (ex.Message.ToLower().Contains("duplicate"))
        {
            MessageBox.Show("Already exists", "Duplicate entry", MessageBoxButton.OK);
            return Task.CompletedTask;
        }
        else
        {
            StorageException se = new StorageException(ex.Message, filename, number);
            Console.WriteLine("Well");
            return Task.FromException(se);
        }
    }
}

如您所见,我已经在尝试获取异常,但我似乎无法追踪它(如果这有意义的话)。

那么我该如何处理抛出的异常,以确保文件名在第一次失败时会再次尝试?

编辑: 背景队列类:

public class BackgroundQueue
{
    private Task previousTask = Task.FromResult(true);
    private object key = new object();
    public Task QueueTask(Action action)
    {
        lock (key)
        {
            previousTask = previousTask.ContinueWith(t => action()
                , CancellationToken.None
                , TaskContinuationOptions.None
                , TaskScheduler.Default);
            return previousTask;
        }
    }

    public Task<T> QueueTask<T>(Func<T> work)
    {
        lock (key)
        {
            var task = previousTask.ContinueWith(t => work()
                , CancellationToken.None
                , TaskContinuationOptions.None
                , TaskScheduler.Default);
            previousTask = task;
            return task;
        }
    }
}

最佳答案

尚不清楚 bq 是什么是,但我猜测它本质上是 Queue<Func<Task>> 的包装器- 即调用时启动异步操作的事物队列。如果是这种情况,那么您的工作循环应该(如果它打算保留顺序)执行以下操作:

async Task RunQueue() {
    while(keepGoing) {
        if(no work) await work; // somehow, not shown; needs to be thread-safe

        Func<Task> nextAction = queue.ThreadSafeDequeue();
        try {
            await nextAction();
        } catch(Exception ex) {
            DoSomethingUseful(ex);
        }
    }
}

重要的是await它等待异步完成,并重新引发观察到的任何异常。

但是,请注意,在您的具体情况Insert正在完成的工作实际上并不是异步的 - 它总是同步完成(或失败)。这不一定是一个问题,但是:像这样的数据访问是异步的主要候选者。

关于c# - 如何处理异步运行方法中的异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49509748/

相关文章:

task - Scrum 板/任务板和颜色编码

c# - 如何在 C# 项目中使用 Scintilla .NET?

c# - 如何在网页中播放音频声音作为背景音乐

c# - 可以将null插入到Cache中吗?

mysql - 如何检查mysql中所有选定行中是否存在一个值?

mysql - 查询以显示第一个 future 事件和之后显示当前事件的时间事件

c# - 顶级任务导致错误 "The current SynchronizationContext may not be used as a TaskScheduler."

gradle - 如何修改自定义Gradle任务可用的类?

c# - C# 中的智能 pretty-print 日期时间

PHP - mysqli 尝试进行选择查询时出现错误 1064