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

时间:2018-03-27 09:54:22

标签: 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);
    }
}

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

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

正如你所看到的,我已经试图获得一个例外,但如果有意义,我似乎无法追踪它。

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

编辑: Backgroundqueue类:

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

1 个答案:

答案 0 :(得分:1)

我不清楚String withoutEmojis = EmojiParser.removeAllEmojis(input); 是什么,但我猜测它本质上是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正在完成的工作实际上不是异步 - 它将始终同步完成(或失败)。这个不一定是问题,但是:像这样的数据访问是异步的主要候选者。