Search code examples
c#.net-4.0crystal-reportstask-parallel-librarytask

Generate file in new task without blocking UI and handle exceptions


I trying to build .NET 4.0 application that will generate Crystal Reports files. I have a working version, but everything is working synchronously - after I click generate button applications freezes for 5 seconds.
Instead of that I would like to show progress indicator that will say that file is generating, but I have problem with my code.

My method that generate report looks like this:

public static Task<string> GenerateLetter()
{
    const string destinationLocation = @"C:\Export";
    const string source = @"C:\Test_Report.rpt";
     return Task<string>.Factory.StartNew(() =>
    {
        if (File.Exists(source))
        {
            var crReportDocument = new ReportDocument();
            crReportDocument.Load(source);
            var destinationFolder = new DirectoryInfo(destinationLocation);
            if (!destinationFolder.Exists)
                destinationFolder.Create();
            var timeStamp = DateTime.Now.ToString().Replace(":", "").Replace(" ", "").Replace("-", "");
            var destination = Path.Combine(destinationFolder.FullName, timeStamp + ".pdf");

            var crDiskFileDestinationOptions = new DiskFileDestinationOptions { DiskFileName = destination };
            var crExportOptions = crReportDocument.ExportOptions;
            {
                crExportOptions.DestinationOptions = crDiskFileDestinationOptions;
                crExportOptions.ExportDestinationType = ExportDestinationType.DiskFile;
                crExportOptions.ExportFormatType = ExportFormatType.PortableDocFormat;
            }
            try
            {
                crReportDocument.Export();
                return destination;
            }
            catch (Exception ex)
            {
                throw new SystemException("Error exporting!", ex);
            }
        }
        throw new FileNotFoundException("Report file not found!", source);
    });
}

Method is returning localization of generated file or it throws exceptions if something goes wrong.

In my form I've placed a button and a marquee progress bar. I've attached this handler to button's click:

private void button1_Click(object sender, EventArgs e)
{
    progressBar1.Visible = true;
    try
    {
        Task<string> xx = ReportGenerator.GenerateLetter();
        MessageBox.Show(xx.Result);
        progressBar1.Visible = false;
    }
    catch (AggregateException ae)
    {
        ae.Handle(x =>
        {
            if (x is FileNotFoundException)
            {
                var ex = x as FileNotFoundException;
                MessageBox.Show(ex.Message,"File not found");
                progressBar1.Visible = false;
            }
            else if (x is SystemException)
            {
                var ex = x as SystemException;
                MessageBox.Show(ex.Message,"Other exception");
                progressBar1.Visible = false;
            }
            return true;
        });
    }
}

My questions:

  1. How can I fix this? So after I click button UI won't freeze.
  2. Can I do this with .NET 4.0 or do I must use 4.5
  3. Can I hide progressbar always after task is completed (successful of with exception)? In jQuery I can use deffered.always, is there something like this in C#?

Solution

  • Task is the good point to start with.

    You can use them either in 4.0 and in 4.5 .net framework.

    In order to ensure that your TPL Task runs on the thread, different from the UI thread, you should use TaskScheduler.Default (look at this thread, for example).

    So you should do something like this:

    Task<string>.Factory.StartNew(() =>
    {
        // Your logic here
    }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
    .ContinueWith((p) =>
    {
         // This will be UI thread
         // p is the parent task
    
          progressBar1.Visible = false;
    
         // if parent task has faulted
         if (p.IsFaulted)
         {
              // Do with p.Exception field what ever you want - log it, show it.
              // for .net 4.0 you must read this property in order to prevent
              // application failure
    
              MessageBox.Show(p.Exception.Message);
              return;
         }
    
         // Here you know all about the parent task, so you can do the logic you want:
    
         MessageBox.Show(p.Result);
    
    }, TaskScheduler.FromCurrentSynchronizationContext());
    

    Continuation task will be performed on the UI thread, because of this TaskScheduler.FromCurrentSynchronizationContext() parameter.

    In .net 4.0 you also have to handle exceptions from the continuation task. You can do it using try-catch block, for example.