The following sample code snippet implements the async
multitasking functionality, namely: reading the list of Stock Prices (in particular, corresponding to Dow Jones Industrial Average index) online and storing them asynchronously in 3 various Collection.Generics
:
Dictionary<string,double>
SortedDictionary<string,double>
and
ConcurrentDictionary<string,double>
.
The app has been tested and all 3 Generics seem to perform normally without any concurrency (racing condition) issues observed so far.
My question: even though the quick test resulted in all positive outcomes, is it considered safe to use a Dictionary<string,double>
or SortedDictionary<string,double>
in this type of async/await
multitasking WPF implementation, or it still requires the use of ConcurrentDictionary<string,double>
in order to avoid possible concurrency/race conditions issues?
Important: for added clarity, please notice that this question is specific to the async/await
multitasking implementation (not just general TPL stuff). In the await
implementation it’s perfectly fine, for example, to access directly any UI Control (e.g. TextBox
) with NO risk of concurrency/racing issues whatsoever. I would assume that the same consideration will apply to the Windows class variables as well (in particular, to any Generics data structures like the Dictionaries used in this sample) making it safe to use them in the same manner without any need for additional thread synchronization/interlocking (even though the regular Collections/Dictionary per se are not thread-safe, but await
is possibly taking care of the synchronization issue).
Listing 1. Sample app reading stock quotes online via multiple async
Tasks
using System;
using System.Windows;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Concurrent;
// sample stock quote project implementing multiple async Tasks
// and various Dictionary objects to store results
namespace QuoteServer
{
public partial class MainWindow : Window
{
// quote web service root Url
private const string _baseUrl = @"http://finance.yahoo.com/d/quotes.csv?f=l1&s=";
/// <summary>
/// Sample List of 30 Stock Symbols (Dow Jones Industrial Average)
/// </summary>
List<string> ListDJIA = new List<string>
{
"AAPL", "AXP", "BA", "CAT", "CSCO", "CVX", "DD", "DIS", "GE", "GS",
"HD", "IBM", "INTC", "JNJ", "JPM", "KO", "MCD", "MMM", "MRK", "MSFT",
"NKE", "PFE", "PG", "TRV", "UNH", "UTX", "V", "VZ", "WMT", "XOM"
};
// store results (multiple stock Px) of asynchronous Tasks in various Dictionary objects
private Dictionary<string, double> _dQuote = new Dictionary<string,double>();
private ConcurrentDictionary<string, double> _cdQuote = new ConcurrentDictionary<string,double>();
private SortedDictionary<string, double> _sdQuote = new SortedDictionary<string,double>();
private CancellationTokenSource _CTS;
public MainWindow(){ InitializeComponent();}
/// <summary>start multiple async Tasks to get quote list</summary>
private async void startButton_Click(object sender, RoutedEventArgs e) {
_CTS = new CancellationTokenSource();
try{
await AccessWebAsync(_CTS.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException){
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch { resultsTextBox.Text += "\r\nDownloads failed.\r\n"; }
finally { _CTS = null; }
}
/// <summary>cancel async Tasks</summary>
private void cancelButton_Click(object sender, RoutedEventArgs e) {
if (_CTS != null) _CTS.Cancel();
}
/// <summary>
/// access web service in async mode to run multiple Tasks
/// </summary>
/// <param name="CT">CancellationToken</param>
/// <returns>Task</returns>
async Task AccessWebAsync(CancellationToken CT)
{
_dQuote.Clear();
_cdQuote.Clear();
_sdQuote.Clear();
resultsTextBox.Clear();
HttpClient _httpClient = new HttpClient();
List<Task> _taskList = new List<Task>();
foreach (string _symbol in ListDJIA)
{
_taskList.Add(GetQuoteAsync(_symbol, _httpClient, CT));
}
await Task.WhenAll(_taskList);
// test if count is 30 (DJIA stocks)
resultsTextBox.Text += String.Concat("Dictionary Items: ", _dQuote.Count, Environment.NewLine);
resultsTextBox.Text += String.Concat("Dictionary Items: ", _cdQuote.Count, Environment.NewLine);
resultsTextBox.Text += String.Concat("Dictionary Items: ", _sdQuote.Count, Environment.NewLine);
}
/// <summary>
/// Get quote from web in async mode
/// </summary>
/// <param name="Symbol">string</param>
/// <param name="client">HttpClient</param>
/// <param name="CT">CancellationToken</param>
/// <returns>Task</returns>
async Task GetQuoteAsync(string Symbol, HttpClient client, CancellationToken CT) {
try {
HttpResponseMessage _response = await client.GetAsync(_baseUrl + Symbol, CT);
// bytes array
byte[] _arrContentBytes = await _response.Content.ReadAsByteArrayAsync();
// bytes array to string
string quote = System.Text.Encoding.UTF8.GetString(_arrContentBytes);
// try parse Stock Px as double
double _dbl;
if (double.TryParse(quote,out _dbl))
{
// add item to dictionary (simplest option)
_dQuote.Add(Symbol, _dbl);
// add item to sorted dictionary (desirable option)
_sdQuote.Add(Symbol, _dbl);
// add item to concurrent dictionary (guaranteed thread-safe option)
_cdQuote.TryAdd(Symbol, _dbl);
}
}
catch { throw; }
}
}
}