Search code examples
.netasync-awaitquartz-schedulerquartz.netconfigureawait

GetAwaiter not working to wait for the completion of Quartz library's Clear method - is there a bug in Quartz or a gap in my understanding?


The latest version of the Quartz library switched everything to use async/await. The calling application has synchronous calls that are very embedded in the framework, so I am stuck calling .GetAwaiter()(I know it's not ideal. It's a console app, so deadlocks due to there being only one UI thread should not occur.)

I need to clear out the schedule database, wait for the Clear command to complete, and repopulate the database in certain scenarios. I'm calling .GetAwaiter() on Quartz's Clear method, but it doesn't wait - that is to say, the next line after the .GetAwaiter line runs BEFORE all of the code that's supposed to be awaited.

I guess with a lot of work (and no performance benefit since I need to .GetAwaiter() anyway) I could refactor to at least call Clear() and the methods to schedule more jobs from the same asynchronous method, and call .GetAwaiter() at a higher level, but I'm not sure that would work either - if .GetAwaiter() does not wait as it should, would "await" work any better?

Of course I could write my own sql calls to clear the db directly, which I may have to do in the end, but it doesn't make sense to me to do that when Quartz has a Clear method on its Scheduler.

My call to the Quartz scheduler's Clear method is very simple:

myScheduler.Clear().GetAwaiter();

What I find is that the line of code AFTER that gets called BEFORE the code in Quartz's StdAdoDelegate.ClearData method, and I thought my GetAwaiter() call would prevent that. Is this a bug in Quartz, and if so what's wrong with the Quartz library's code? Or is something wrong with how I'm thinking about it?

Here is an outline of the code called when you call Quartz's Clear() method:

StdScheduler:
    Task Clear(cancellationToken)
        return sched.Clear(cancellationToken);

QuartzScheduler:
    async Task Clear(cancellationToken = default)
        await resources.JobStore.ClearAllSchedulingData(cancellationToken).ConfigureAwait(false);

JobStoreSupport:
    Task ClearAllSchedulingData
        return ExecuteInLock(LockTriggerAccess, conn => ClearAllSchedulingData(conn, cancellationToken), cancellationToken);

    async Task ExecuteInLock(lockName, txCallback, cancellationToken=default)
        await ExecuteInLock<object>(lockName, async conn =>
            {
                await txCallback(conn).ConfigureAwait(false);
                return null;
            }, cancellationToken).ConfigureAwait(false);

JobStoreTX (which inherits from JobStoreSupport):
    Task<T> ExecuteInLock<T>(lockName, txCallback, cancellationToken)
        return ExecuteInNonManagedTXLock(lockName, txCallback, cancellationToken);

JobStoreSupport:   
    Task<T> ExecuteInNonManagedTXLock(lockName, txCallback, cancellationToken)
        T result = await txCallback(conn).ConfigureAwait(false);

    (stack trace returns first to ExecuteInLock<T>, then to ExecuteInLock, and then to ClearAllSchedulingData as callback is called)

    async Task ClearAllSchedulingData(conn, cancellationToken)
        await Delegate.ClearData(conn, cancellationToken).ConfigureAwait(false);

StdAdoDelegate:
    async Task ClearData(conn, cancellationToken=default)
            DbCommand ps = PrepareCommand(conn, ReplaceTablePrefix(SqlDeleteAllSimpleTriggers));
            await ps.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
            ps = PrepareCommand(conn, ReplaceTablePrefix(SqlDeleteAllSimpropTriggers));
            await ps.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
            And more similar calls...

Many thanks for your assistance!!


Solution

  • .GetAwaiter() only returns a TaskAwaiter, you need to request the result from the awaiter, even if it's just a Task with none (void) result type. To wait for the completion of the task, just call: myScheduler.Clear().GetAwaiter().GetResult();