简体   繁体   中英

Reporting/logging from with in a Task

I have an async call that throws an exception if the given record fails processing. That exception is caught as aggregated exception. There is now, a requirement that I have to log a warning message from my async method. I cannot log to TextBox/Grid as it is not allowed to access controls over different threads and I cannot throw an exception as I actually want to log and continue that task. Here is the parent code that launches a task:

private List<OrganizationUser> GenerateDataList(string importFilePath, int lastUserId = -1)
{
  var resultList = new List<OrganizationUser>();

  // Note: ReadLine is faster than ReadAllLines
  var lineCount = File.ReadLines(importFilePath).Count();
  LogMessage(new LogEntry(String.Format(" - File has {0} lines.", lineCount)));
  var consistancyCounter = 0;

  ResetProgress();

  using (var fs = File.Open(importFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
  {
    using (var bs = new BufferedStream(fs))
    {
      using (var sr = new StreamReader(bs))
      {
        string readLine;
        while ((readLine = sr.ReadLine()) != null)
        {
          if (string.IsNullOrEmpty(readLine) || readLine == "---EOF---")
          {
            break;
          }

          try
          {
            var processLineTask = Task.Run(() => GenerateDataListInternal(nextId++, localReadLine, localConsistancyCounter));
            processLineTask.Wait();

            var result = processLineTask.Result;

            if (result != null)
            {
              resultList.Add(result);
            }
          }
          catch (AggregateException exp)
          {
            if (exp.InnerExceptions.Count == 1 && exp.InnerExceptions.Any(x => x is DataFileBadColumnNumbers || x is DataFileGenerateListException))
            {
              LogMessage(new LogEntry(exp.InnerExceptions[0].Message, LogEntryType.Warning));
            }
            else if (exp.InnerExceptions.Count == 1 && exp.InnerExceptions.Any(x => x is IndexOutOfRangeException))
            {
              LogMessage(new LogEntry(String.Format(" - Data cannot be parsed at line #{0}. Data is: {1}", localConsistancyCounter + 1, localReadLine), LogEntryType.Warning));
            }
            else
            {
              throw;
            }
          }
        }

        if (ProgressBarImport.Value <= ProgressBarImport.Maximum)
        {
          ProgressBarImport.PerformStep();
        }   
      }
    }
  }
}

In the above code, GenerateDataListInternal is the method that throws exceptions and now needs to log.

How can I log from within the GenerateDataListInternal method? I have tried the delegate approach and that simply hangs the application. I have a method that logs to Console, Grid and text file (in that order). Any call to that method from asynced methods fail due to Cross thread operation.

This is already provided through the System.IProgress interface and the Progress class, where T can be any class, allowing you to report much more than a simple progress percentage.

IProgress<T>.Report allows you to report progress (or anything else) from inside an asynchronous operation without worrying who will actually handle the report.

Progress<T> will call an action and/or raise an event on the thread where it was created (eg. the UI thread) whenever your task calls .Report

This example from the .NET Framework Blog displays how easy it is to use IProgress<T> :

async Task<int> UploadPicturesAsync(List<Image> imageList, IProgress<int> progress)
{
        int totalCount = imageList.Count;
        int processCount = await Task.Run<int>(() =>
        {
            int tempCount = 0;
            foreach (var image in imageList)
            {
                //await the processing and uploading logic here
                int processed = await UploadAndProcessAsync(image);
                if (progress != null)
                {
                    progress.Report((tempCount * 100 / totalCount));
                }
                tempCount++;
            }

            return tempCount;
        });
        return processCount;
}

The async process starts with this code:

private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
//construct Progress<T>, passing ReportProgress as the Action<T> 
    var progressIndicator = new Progress<int>(ReportProgress);
//call async method
    int uploads=await UploadPicturesAsync(GenerateTestImages(), progressIndicator);
}

Note that progressIndicator is created in the UI thread, so the ReportProgress method will be called on the UI thread.

UPDATE

From the comments it sounds like you are trying to create your own logging solution and have issues with accessing the log file from multiple threads.

The best solution here would be to use a logging library like log4net , NLog or even .NET's Diagnostic classes. All of them work without issues with multiple threads.

IProgress<T> can still help here, as the delegate that handles a Report event can simply write the message to a log file you already created on the UI thread.

You could write something like this:

var progress = new Progress<LogEntry>(entry =>
{
   _logFile.WriteLine("{0} : {1}",entry.EntryType,entry.Message);
});

and report from inside a Task with:

var entry=new LogEntry(...);
progress.Report(entry);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM