Async/await in Parallel.ForEach
Is it ok to have an async void method?


Referencing Async/Await - Best Practices in Asynchronous Programming

As already stated in the OP async void should be avoided as much as possible. 

The one exception to that rule being for event handlers, which can be the loophole to achieving the desired behavior while still having the ability to catch and handle any thrown exceptions.

Create an event 

event EventHandler QueryReportCallbackEvent = delegate { };


to be raised by the timer callback

private void QueryReportTimerCallback(object state) {
    QueryReportCallbackEvent(this, EventArgs.Empty);
}


The event handler allows async void, so now you can safely do

private async void QueryReportCallbackEventHandler(object sender, EventArgs e) {
    if (await semaphoreQueryReportSlim.WaitAsync(10))
        await queryReportsCore();
}



  Is it ok to have await inside Parallel.ForEach?


NO!!!!

The async lambda 

async (configuration) =&gt; ...


will be converted to async void which takes us right back to what was said in the beginning. async void BAD!!!

Instead refactor that lambda out into its own method that returns a Task

private async Task HandleReport(MachineConfiguration configuration) {
    if (configuration.IsConnectionValid) {
        var queryReport = new QueryReport(configuration, ReportConfigurations, fileContainer, applicationConfiguration, logger);
        await QueryAReport(configuration, queryReport);
    }
}


and this will now allow for the use of Task.WhenAll with all the machine configurations retrieved from the query.

var tasks = machineConfigurations
                .Where(x =&gt; x.QueryReport)
                .Select(configuration =&gt; HandleReport(configuration));

await Task.WhenAll(tasks);


This actually removes the need for the Paralell.ForEach.

Here is the complete code for what was described above.

//CTOR
public MyClass() {
    QueryReportCallbackEvent += QueryReportCallbackEventHandler;
    var QueryReportTimer = new Timer(QueryReportTimerCallback, null, TimeSpan.Zero, TimeSpan.FromSeconds(15));
}

event EventHandler QueryReportCallbackEvent = delegate { };

private void QueryReportTimerCallback(object state) {
    QueryReportCallbackEvent(this, EventArgs.Empty);
}

private async void QueryReportCallbackEventHandler(object sender, EventArgs e) {
    if (await semaphoreQueryReportSlim.WaitAsync(10))
        await queryReportsCore();
}

private async Task queryReportsCore() {
    try {
        if (machineConfigurations != null) {
            var tasks = machineConfigurations
                            .Where(x =&gt; x.QueryReport)
                            .Select(configuration =&gt; HandleReport(configuration));

            await Task.WhenAll(tasks);
        }
    } catch (Exception e) {
        logger.LogError(e, e.Message);
    } finally {
        semaphoreQueryReportSlim.Release();
    }
}

private async Task HandleReport(MachineConfiguration configuration) {
    if (configuration.IsConnectionValid) {
        var queryReport = new QueryReport(configuration, ReportConfigurations, fileContainer, applicationConfiguration, logger);
        await QueryAReport(configuration, queryReport);
    }
}


Lastly take note of how the functions were broken down into smaller chunks that allowed for cleaner, more easy to read code.