0

To test the Data Access Layer of a wider project, I have written a simple console app as a test harness to call the DAL.

The saving of the data is a two-step operation. The first step is to save the very basic information from the report and HAS HAS to be succeed.. The second step is to decipher the raw data into the model and then save it but if this fails, its not a big problem and will be reported and addressed later.

The whole process is to run asynchronously (its part of a REST) but the await _context.SaveChangesAsync() calls do not appear to wait and the app exits and no data is saved.

Below is a simplified example of the DB update

public async Task<bool> SubmitFlightReportAsync(string ReportData)
{
    try
    {
        // Save the primary data - MUST Succeed
        _logger.LogInformation("Saving primary data database ");
        Models.MyReport myRpt = new Models.MyReport()
        {
            Status = 1,
            RawData = ReportData
        };
        _context.MyReports.Add(myRpt);

        if (await _context.SaveChangesAsync() > 0)
        {
            _logger.LogInformation("Primary save complete");

            // Now update with all the extracted data - Optional Succeed
            try
            {
                _logger.LogInformation("Extracting secondary data");
                /*
                 * 
                 * Here is where we extract all the information from the Report Data 
                 * and update the Model, before saving
                 * 
                 */
                _logger.LogInformation("Saving extracted data");

                if (await _context.SaveChangesAsync() > 0)
                {
                    _logger.LogDebug("Secondary save complete");
                }

            }
            catch (Exception ex)
            {
                _logger.LogError("Error saving secondary data ");
            }
        }
        else
        {
            _logger.LogInformation("Primary save failed");
        }
    }
    catch (Exception ex)
    {
        _logger.LogCritcal("Something went horrobly wrong");
    }
}

.and is called from the console app as such...

_logger.LogInformation("Starting application");
_fltSvc.SubmitFlightReportAsync("this is the report data as a string")
_logger.LogInformation("All done");

When I run the app, here's the logging that gets reported...

object:Information: Starting application
object:Information: Saving primary data database

... Where's all the other logging messages?

object:Information: All done

As you can see, it gets to the point of the first SaveChangesAsync, waits a little, as if its doing something, and then jumps back to the main thread..... and NO DATA is saved in the database.

I am sure it's something pretty simple, but I just can see it...

Thanks.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
Chris Hammond
  • 2,064
  • 5
  • 27
  • 53
  • 1
    Do you *await* that async method or does the application terminate before the method has a chance to complete? What does your *actual* code look like? Assuming it is `_fltSvc.SubmitFlightReportAsync("this is the report data as a string");`, without `await` there's no guarantee the method completes before `Main` exits. – Panagiotis Kanavos Nov 15 '18 at 08:13
  • Yes, the call to `SubmitFlightReportAsync()` is _await_ ed ... I have also tried `_fltSvc.SubmitFlightReportAsync(fltRpt).ConfigureAwait(false).GetAwaiter().GetResult();` with same result... For the record, the _result_ comes back as `false` indicating it did not get to the completion point. – Chris Hammond Nov 15 '18 at 08:32
  • Post your actual code, including your `Main()` method. Something that reproduces the problem. Don't force people to guess. Don't try things at random either, neither EF or `async/await` are broken. `_fltSvc.SubmitFlightReportAsync(fltRpt).ConfigureAwait(false).GetAwaiter().GetResult` says "block on another thread". If you wanted to block the operation, a simple `.Result` would do. It's not needed though – Panagiotis Kanavos Nov 15 '18 at 08:37
  • 1
    Did you try debugging? You could use that to find out what path the execution takes. – MEMark Nov 15 '18 at 08:42
  • A simple example would show that EF and async/await work perfectly on .NET Core : `public static async Task Main(){ using (var ctx=new MyContext()){ ctx.Reports.Add(new Report..);await ctx.SaveChangesAsync();}`. If you don't await the async operation all the way to the root, you aren't awaiting it, period. – Panagiotis Kanavos Nov 15 '18 at 08:43
  • @ChrisHammond create a new *simple* console app and try to reproduce the problem. Use the absolute minimum code required to reproduce it. That does that code look like? – Panagiotis Kanavos Nov 15 '18 at 08:46
  • 1
    @ChrisHammond `The whole process is to run asynchronously (its part of a REST) ` it's not a *console* application then, it's ASP.NET Core. That means the root of each async operation is the controller action. Your actions should have an `async Task<>` signature, eg `public async Task Post()`. What does your actual code look like? – Panagiotis Kanavos Nov 15 '18 at 09:17
  • OK... Thank you all for the input ... Having gone through the entire code line by line, one single call to an `async` method was missing an await and result assigment... – Chris Hammond Nov 15 '18 at 10:43

2 Answers2

0

Call SubmitFlightReportAsync in your main without await like the following.

public static void Main(string[] args)
{
    Task t = SubmitFlightReportAsync("bla");
    t.Wait(); //This is the line you need to have when working with console apps.
}

Asyny and Await is different while using it in Console app.

Or take a look at the following: async at console app in C#?

Jordy van Eijk
  • 2,718
  • 2
  • 19
  • 37
  • `async Task Main()` is available since C# 7.1 which was released 1 year ago. The question is about .NET Core 2.1 which means the functionality is already available – Panagiotis Kanavos Nov 15 '18 at 09:09
-1

When you call to SubmitFlightReportAsync, you initiate the execution of your operation and then return to the caller. When you then end your application immediately, the task will not have a chance to execute. You have to wait for task completion, before you can assume that everything is done:

_logger.LogInformation("Starting application");
Task submitTask =
    Task.Run(() => _fltSvc.SubmitFlightReportAsync("this is the report data as a string"));
submitTask.Wait();
_logger.LogInformation("All done");

In case you are running this code from the Main method and you are using C# 7.0 or higher, you can also simply create an async Main and await in there:

static async void Main() {
    //...
    _logger.LogInformation("Starting application");
    await _fltSvc.SubmitFlightReportAsync("this is the report data as a string")
    _logger.LogInformation("All done");
    //...
}
Sefe
  • 13,731
  • 5
  • 42
  • 55
  • 1
    This will make things *worse*. `SubmitFlightReportAsync` is already calling an asynchronous operation. Using `Task.Run` and `Wait` won't block the inner async operation. `_fltSvc.SubmitFlightReportAsync(..).Wait()` would block, but isn't needed as long as the rest of the code uses `asyn/await` as it should – Panagiotis Kanavos Nov 15 '18 at 08:40
  • @PanagiotisKanavos This specific overload of `Task.Run` will create a proxy task, which will ensure that the promise-based `async` operation will run on the thread pool. I always do it like that and it works fine. I suggest you check the [official documenation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netcore-2.1#System_Threading_Tasks_Task_Run_System_Func_System_Threading_Tasks_Task__) – Sefe Nov 15 '18 at 08:51
  • Which isn't needed in the first place. DbContext.SaveChangesAsync is *already* an asynchronous operation. The actual operations are executed by asynchronously ADO.NET's ExecuteNonQueryAsync, ExecuteReaderAsync etc. There's a reason such code doesn't appear in any EF or ADO.NET tutorial or open-source project. – Panagiotis Kanavos Nov 15 '18 at 08:54
  • @PanagiotisKanavos ...which will not necessarily run on the thread pool, since it is `async await`, with a proxy task you are guaranteed to run in any context. And at least we're downgraded now from "making matters worse" to "not necessary". – Sefe Nov 15 '18 at 08:57
  • quite the opposite. `async` is just syntactic sugar. `await` *awaits* an already asynchronous operation. And yes, it's worse, not just unnecessary. That's because it *changed the execution itself*, it did *NOT* just create a proxy. `SubmitFlightReportAsync` executes *synchronously* until `await _context.SaveChangesAsync()`. That's what allows all `async/await` methods to modify the UI thread. With Task.Run though a *new* task is actually started that executes everything up to that `await` on a threadpool thread – Panagiotis Kanavos Nov 15 '18 at 08:58
  • To make matters even worse, `.Wait()` will *block* the original caller. Instead of releasing the original thread, we end up with multiple threads *and* a blocked caller – Panagiotis Kanavos Nov 15 '18 at 09:01
  • `it works fine` proving it doesn't work is very easy - create a desktop application and try to modify any UI element before `await`. – Panagiotis Kanavos Nov 15 '18 at 09:08
  • @PanagiotisKanavos The OP specifically created a console application and there is no access to the UI in the method. So why is UI a problem here? A problem btw. that is easily and reliably solved with `ISynchronizeInvoke` in winforms and `Dispatcher` in WPF. – Sefe Nov 15 '18 at 10:22
  • Since .NET 4.5 that's the job of *await*, not Dispatcher. You *don't* need Dispatcher or Invoke (which can deadlock) when you already have await. In any case read the question again - it's an ASP.NET Core application, not a console. Blocking with `.Wait()` would waste the dedicated thread already serving the request just to move processing to another thread. With async/await, no thread is used/blocked while awaiting – Panagiotis Kanavos Nov 15 '18 at 10:31