Without blocking UI using async and await - c#

My requirement is, need to render all framework elements which are bound with data using async and await.
Tried below possibilities:
If i am using asyncmethod().result it will block the UI and it
will be waiting for a long time to completion.
If I am using await keyword the controlstoconfigure foreach(2nd
one) will be hit for before completion of parallel.foreach()
So can you please suggest me for rendering elements, call the 2nd for each after completion of parallel foreach or without blocking UI?
Code snippet:
1)Renderingwidget - this method is used to get framework elements in the collection which is bound with data.
2)FetchData - this method is used to get the data from the server for particular framework element.
3)GetTablefromserver - this method is used to get the data based on queries.
public async void RenderingWidget()
{
ConcurrentDictionary<string, EngineChangedEventArgs> controlsToConfigure = new ConcurrentDictionary<string, EngineChangedEventArgs>();
foreach (var engines in MainWindow.ViewModel.RelationalDataManagerList.Values)
{
Dictionary<string, FrameworkElement> controlcollection = CurrentDashboardReport.WidgetCollection;
Parallel.ForEach(controlcollection, async item =>
{
try
{
try
{
controlsToConfigure.TryAdd(item.Key, await FetchDataInParallel(MainWindow.ViewModel.RelationalDashboardReportList[engines.DataSourceName].Reports, item.Key,));
}
catch (Exception ex)
{
ExceptionLog.WriteExceptionLog(ex, null);
throw new ParallelException(ex, item.Key);
}
}
catch (ParallelException ex)
{
exceptions.Enqueue(ex);
ExceptionLog.WriteExceptionLog(ex, GeneratedQueryText);
}
});
if (exceptions.Count > 0)
{
foreach (var nonRenderedControls in exceptions)
{
controlsToConfigure.TryAdd(nonRenderedControls.ReportName, await FetchDataInParallel(CurrentDashboardReport.Reports, nonRenderedControls.ReportName));
}
}
}
foreach (var control in controlsToConfigure)
{
(CurrentDashboardReport.WidgetCollection[control.Key] as DashboardDataControl).CurrentDashboardReport.OnActiveColumnsChanged(control.Value);
}
}
public async Task<EngineChangedEventArgs> FetchDataInParallel(RelationalReportCollection reports, string myReport)
{
var dataTable = await GetTableFromServer(reports.Query);
eventArgs = new EngineChangedEventArgs
{
ItemsSource = dataTable
};
return eventArgs;
}
public async Task<DataTable> GetTableFromServer(string query)
{
var resultTable = new DataTable
{
Locale = CultureInfo.InvariantCulture
};
SqlConnection sqlConnection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand(query, sqlConnection)
SqlDataReader dataReader = null;
try
{
if (sqlConnection.State != ConnectionState.Open)
{
sqlConnection.Open();
}
dataReader =await command.ExecuteReaderAsync();
resultTable.Load(dataReader);
return resultTable;
}
finally
{
if (dataReader != null)
{
dataReader.Dispose();
}
command.Dispose();
return resultTable;
}
}

Parallel.ForEach doesn't play well with async/await as you release the thread for the duration of the async call. Please refer to the following question for more information about this: Nesting await in Parallel.ForEach
You may just call Parallel.ForEach on a thread pool thread and then use the Task.WhenAll method to wait until all tasks have completed Before you continue:
public async void RenderingWidget()
{
ConcurrentDictionary<string, EngineChangedEventArgs> controlsToConfigure = new ConcurrentDictionary<string, EngineChangedEventArgs>();
List<Task> tasks = new List<Task>();
foreach (var engines in MainWindow.ViewModel.RelationalDataManagerList.Values)
{
Dictionary<string, FrameworkElement> controlcollection = CurrentDashboardReport.WidgetCollection;
tasks.Add(Task.Run(() =>
{
Parallel.ForEach(controlcollection, item =>
{
try
{
try
{
controlsToConfigure.TryAdd(item.Key, FetchDataInParallel(MainWindow.ViewModel.RelationalDashboardReportList[engines.DataSourceName].Reports, item.Key).Result);
}
catch (Exception ex)
{
ExceptionLog.WriteExceptionLog(ex, null);
throw new ParallelException(ex, item.Key);
}
}
catch (ParallelException ex)
{
exceptions.Enqueue(ex);
ExceptionLog.WriteExceptionLog(ex, GeneratedQueryText);
}
});
if (exceptions.Count > 0)
{
foreach (var nonRenderedControls in exceptions)
{
controlsToConfigure.TryAdd(nonRenderedControls.ReportName, FetchDataInParallel(CurrentDashboardReport.Reports, nonRenderedControls.ReportName).Result);
}
}
}));
}
await Task.WhenAll(tasks);
foreach (var control in controlsToConfigure)
{
(CurrentDashboardReport.WidgetCollection[control.Key] as DashboardDataControl).CurrentDashboardReport.OnActiveColumnsChanged(control.Value);
}
}
public async Task<EngineChangedEventArgs> FetchDataInParallel(RelationalReportCollection reports, string myReport)
{
var dataTable = await GetTableFromServer(reports.Query).ConfigureAwait(false);
return new EngineChangedEventArgs
{
ItemsSource = dataTable
};
}

Related

Asynchronous QueryQueuedBuilds

Our program uses the QueryQueuedBuilds from Microsoft.TeamFoundation.Build.Server to get all the queued builds from our build server.
So far we did it synchrone from a DispatcherTimer tick method, but the UI gets non-responsive sometimes for more than 1000 ms. This is the original call:
public List<QueuedBuild> GetQueuedBuilds()
{
List<QueuedBuild> queuedBuilds = new List<QueuedBuild>();
foreach (var project in _teamProjectList)
{
IQueuedBuildSpec queuedBuildSpec = _buildServer.CreateBuildQueueSpec(project.Name);
IQueuedBuildQueryResult queuedBuildQueryResult = _buildServer.QueryQueuedBuilds(queuedBuildSpec);
if (queuedBuildQueryResult.QueuedBuilds.Length > 0)
{
foreach (var result in queuedBuildQueryResult.QueuedBuilds)
{
queuedBuilds.Add(new QueuedBuild()
{
// Set properties from result
});
}
}
}
return queuedBuilds;
}
But the line with the QueryQueuedBuilds takes a long time.
I discovered there are asynchronous methods BeginQueryQueuedBuilds and EndQueryQueuedBuilds, but I cannot find examples how to use it. This is what I got so far:
public List<QueuedBuild> GetQueuedBuilds()
{
List<QueuedBuild> queuedBuilds = new List<QueuedBuild>();
Collection<IQueuedBuildSpec> queuedBuildSpecs = new Collection<IQueuedBuildSpec>();
foreach (var project in _teamProjectList)
{
queuedBuildSpecs.Add(_buildServer.CreateBuildQueueSpec(project.Name));
}
IAsyncResult asyncResult = _buildServer.BeginQueryQueuedBuilds(queuedBuildSpecs.ToArray(),
OnBeginQueryQueuedBuilds, queuedBuilds);
// What to do with asyncResult?
return queuedBuilds; // How to return queuedBuilds?
}
private void OnBeginQueryQueuedBuilds(IAsyncResult ar)
{
if (!(ar.AsyncState is List<QueuedBuild> queuedBuilds))
{
return;
}
IQueuedBuildQueryResult[] queryResults = _buildServer.EndQueryQueuedBuilds(ar);
foreach (var queuedBuildQueryResult in queryResults)
{
foreach (var result in queuedBuildQueryResult.QueuedBuilds)
{
queuedBuilds.Add(new QueuedBuild()
{
// Set properties from result
});
}
}
// How to get the queuedBuilds back to the GUI?
}
The callback method works. But how do I get the queuedBuilds back to my GUI?
I finally solved the puzzle similar to this solution.
Most important changes I did:
The callback method isn't used anymore.
The IAsyncResult is now passed to a Task.Factory.FromAsync() method and the task is awaited.
This is what I ended up with:
public async Task<List<QueuedBuild>> GetQueuedBuilds()
{
Collection<IQueuedBuildSpec> queuedBuildSpecs = new Collection<IQueuedBuildSpec>();
foreach (var project in _teamProjectList)
{
queuedBuildSpecs.Add(_buildServer.CreateBuildQueueSpec(project.Name));
}
IAsyncResult asyncResult = _buildServer.BeginQueryQueuedBuilds(queuedBuildSpecs.ToArray(), null, null);
var task = Task.Factory.FromAsync(asyncResult, _buildServer.EndQueryQueuedBuilds);
IQueuedBuildQueryResult[] queuedBuildQueryResults = await task;
return FinishQueryQueuedBuilds(queuedBuildQueryResults);
}
private List<QueuedBuild> FinishQueryQueuedBuilds(IQueuedBuildQueryResult[] queuedBuildQueryResults)
{
List<QueuedBuild> queuedBuilds = new List<QueuedBuild>();
foreach (var queuedBuildQueryResult in queuedBuildQueryResults)
{
foreach (var result in queuedBuildQueryResult.QueuedBuilds)
{
queuedBuilds.Add(new QueuedBuild()
{
// Set properties from result
});
}
}
return queuedBuilds;
}
I hope this can help someone else with a similar problem.

How to concurrently complete HTTP calls on an observable collection?

In the WPF .net core app there is the following:
An Observable Collection of items (itemObservCollection).
A static readonly HttpClient _httpclient
XML Responses
I am making a URL call to the api on each item in the observable collection (0 to 1000 items in collection). The return is XML. The XML is parsed using XElement. The property values in the observable collection are updated from the XML.
Task.Run is used to run the operation off the UI thread. Parallel.Foreach is used to make the calls in Parallel.
I feel I have made the solution overly complicated. Is there a way to simplify this? UpdateItems() is called from a button click.
private async Task UpdateItems()
{
try
{
await Task.Run(() => Parallel.ForEach(itemObservCollection, new ParallelOptions { MaxDegreeOfParallelism = 12 }, async item =>
{
try
{
var apiRequestString = $"http://localhost:6060/" + item.Name;
HttpResponseMessage httpResponseMessage = await _httpclient.GetAsync(apiRequestString);
var httpResponseStream = await httpResponseMessage.Content.ReadAsStreamAsync();
StringBuilder sb = new StringBuilder(1024);
XElement doc = XElement.Load(httpResponseStream);
foreach (var elem in doc.Descendants())
{
if (elem.Name == "ItemDetails")
{
var itemUpdate = itemObservCollection.FirstOrDefault(updateItem => updateItem.Name == item.Name);
if (itemUpdate != null)
{
itemUpdate.Price = decimal.Parse(elem.Attribute("Price").Value);
itemUpdate.Quantity = int.Parse(elem.Attribute("Quantity").Value);
}
}
}
}
catch (Exception ex)
{
LoggerTextBlock.Text = ('\n' + ex.ToString());
}
}));
}
catch (Exception ex)
{
LoggerTextBlock.Text = ('\n' + ex.ToString());
}
}
You could create an array of tasks and await them all using Task.WhenAll.
The following sample code kicks off a task per item in the ObservableCollection<int> and then wait asynchronously for all tasks to finish:
ObservableCollection<int> itemObservCollection =
new ObservableCollection<int>(Enumerable.Range(1, 10));
async Task SendAsync()
{
//query the HTTP API here...
await Task.Delay(1000);
}
await Task.WhenAll(itemObservCollection.Select(x => SendAsync()).ToArray());
If you want to limit the number of concurrent requests, you could either iterate through a subset of the source collecton to send requests in batches or use a SemaphoreSlim to limit the number of actual concurrent requests:
Task[] tasks = new Task[itemObservCollection.Count];
using (SemaphoreSlim semaphoreSlim = new SemaphoreSlim(12))
{
for (int i = 0; i < itemObservCollection.Count; ++i)
{
async Task SendAsync()
{
//query the HTTP API here...
try
{
await Task.Delay(5000);
}
finally
{
semaphoreSlim.Release();
}
}
await semaphoreSlim.WaitAsync();
tasks[i] = SendAsync();
}
await Task.WhenAll(tasks);
}

Asynchronous method does not return control to its caller

I have created a supposed to be asynchronous method. And inside of that it has several await statements.
I am calling this method (in another class) from the main method.
But it doesn't return the control back to the caller (main), to execute the next statement (to get more data),
even though the operation takes long time to complete.
What is wrong here?
Could it be something with the return statement? Or do I need to wrap the logic inside a Task?
internal async static Task<List<Cars>> GetCarsAsync()
{
var cars = new List<Cars>();
try
{
using (var connection = new OracleConnection(ConfigurationManager.ConnectionStrings["KWC"].ConnectionString))
{
await connection.OpenAsync(); //first await
logger.Info("Opened database connection.");
StringBuilder selectStatement = new StringBuilder(Properties.Settings.Default.SelectCarsView);
using (var command = connection.CreateCommand())
{
command.CommandText = selectStatement.ToString();
logger.Info("About to execute following query on database: {0}", command.CommandText);
using (var reader = await command.ExecuteReaderAsync()) //second await
{
logger.Info("Executed query: {0}", command.CommandText);
while (await reader.ReadAsync()) //third await
{
var car = new Car { model = reader.GetString(0) };
//more code
cars.Add(car);
}
}
}
}
return cars;
}
catch (OracleException ex)
{
logger.Fatal(ex.Message);
throw;
}
}
static async Task Main(string[] args)
{
var getCarsTask = CarsManager.GetCarsAsync();
var getSomethingElseTask = DummyManager.GetMoreDataAsync();
// more similar tasks
var cars = await getCarsTask;
var moreData = await getSomethingElseTask;
// more code
}

SqlBulkCopy.WriteToServerAsync does not respect the `await` keyword. Why?

SqlBulkCopy.WriteToServerAsync does not respect the await keyword. Why?
Here is my code:
public async Task UpdateDBWithXML(Action<Func<DataTable, Task>> readXmlInBatches, string hashKey, string hash)
{
using (var transaction = this.Context.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
using (var bulk = new SqlBulkCopy((SqlConnection)this.Connection, SqlBulkCopyOptions.Default, (SqlTransaction)transaction.UnderlyingTransaction))
{
//this.Context.Database.ExecuteSqlCommand("DELETE FROM [dbo].[LegalContractorTemps]");
bulk.DestinationTableName = "LegalContractorTemps";
readXmlInBatches(async (DataTable table) =>
{
if (bulk.ColumnMappings.Count == 0)
{
foreach (DataColumn column in table.Columns)
{
bulk.ColumnMappings.Add(new SqlBulkCopyColumnMapping(column.ColumnName, column.ColumnName));
}
}
await bulk.WriteToServerAsync(table);
});
await this.Context.Database.ExecuteSqlCommandAsync(
"EXECUTE dbo.LegalContractorsDataSynchronize #hashKey, #hash",
new SqlParameter("#hashKey", hashKey),
new SqlParameter("#hash", hash)
);
transaction.Commit();
}
}
In the readXmlInBatches parameter I pass the following function as an argument:
public void ReadXMLInBatches(Func<DataTable, Task> processBatch)
{
int batchSize = 10000;
var table = new DataTable();
foreach (var col in columnNames)
{
table.Columns.Add(col);
}
using (var reader = new StreamReader(pathToXml, Encoding.GetEncoding(encoding)))
using (var xmlReader = XmlReader.Create(reader))
{
string lastElement = null;
DataRow lastRow = null;
while (xmlReader.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
if (xmlReader.Name == "RECORD")
{
if (table.Rows.Count >= batchSize)
{
processBatch(table);
table.Rows.Clear();
}
lastRow = table.Rows.Add();
}
lastElement = xmlReader.Name;
break;
case XmlNodeType.Text:
ReadMember(lastRow, lastElement, xmlReader.Value);
break;
}
}
if (table.Rows.Count > 0)
{
processBatch(table);
table.Rows.Clear();
}
}
}
I have in the XML something about 1.7 million records. After my program have read a few batches I am getting the error:
System.Data.RowNotInTableException: 'This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.'
I researched the source code of the SqlBulkCopy. And found the method which throws an error:
public Task WriteToServerAsync(DataTable table, DataRowState rowState, CancellationToken cancellationToken) {
Task resultTask = null;
SqlConnection.ExecutePermission.Demand();
if (table == null) {
throw new ArgumentNullException("table");
}
if (_isBulkCopyingInProgress){
throw SQL.BulkLoadPendingOperation();
}
SqlStatistics statistics = Statistics;
try {
statistics = SqlStatistics.StartTimer(Statistics);
_rowStateToSkip = ((rowState == 0) || (rowState == DataRowState.Deleted)) ? DataRowState.Deleted : ~rowState | DataRowState.Deleted;
_rowSource = table;
_SqlDataReaderRowSource = null;
_dataTableSource = table;
_rowSourceType = ValueSourceType.DataTable;
_rowEnumerator = table.Rows.GetEnumerator();
_isAsyncBulkCopy = true;
resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); //It returns Task since _isAsyncBulkCopy = true;
}
finally {
SqlStatistics.StopTimer(statistics);
}
return resultTask;
}
I noticed the field _isBulkCopyingInProgress and decided to check it while debugging. And I found out that when the error is thrown the field is true. How is that possible? I would expect the bulk insert to happen first (before the execution continues and the WriteToServerAsync will be called a second time) since I add the await here: await bulk.WriteToServerAsync(table);.
What could I be missing?
You are passing an asynchronous function to ReadXMLInBatches, but it's execution isn't being awaited inside your method, therefore ReadXMLInBatches may terminate before all the calls to WriteToServerAsync have completed.
Try the following changes:
public async Task ReadXMLInBatchesAsync(Func<DataTable, Task> processBatch)
{
//...
await processBatch(table);
//...
}
public async Task UpdateDBWithXML(Func<Func<DataTable, Task>, Task> readXmlInBatches, string hashKey, string hash)
{
//...
await readXmlInBatches(async (DataTable table) =>
//...
}

C# How To Achieve Monitor.Enter/Exit With Task Based Async

This is my code that works. I wouldn't have to do this if it weren't task based async, but using Monitor.Enter/Exit results in this problem Object synchronization method was called from an unsynchronized block of code. Exception on Mutex.Release()
People have mentioned using AutoResetEvent, and SemaphoreSlim, but I'm not quite sure which pattern fits.
private bool fakelock;
internal async Task<byte[][]> ExchangeCore(byte[][] apdus)
{
if (apdus == null || apdus.Length == 0)
return null;
List<byte[]> resultList = new List<byte[]>();
var lastAPDU = apdus.Last();
while (fakelock)
{
await Task.Delay(100);
}
fakelock = true;
foreach (var apdu in apdus)
{
await WriteAsync(apdu);
var result = await ReadAsync();
resultList.Add(result);
}
fakelock = false;
return resultList.ToArray();
}
You could maybe use a SemaphoreSlim which supports async.
private static SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
internal async Task<byte[][]> ExchangeCore(byte[][] apdus)
{
if (apdus == null || apdus.Length == 0)
return null;
await Semaphore.WaitAsync();
try
{
List<byte[]> resultList = new List<byte[]>();
foreach (var apdu in apdus)
{
await WriteAsync(apdu);
var result = await ReadAsync();
resultList.Add(result);
}
return resultList.ToArray();
}
finally
{
Semaphore.Release();
}
}

Categories

Resources