Concurrency Exception in DataLayer - c#

I have a list of items, and for each item, I need to execute series of tasks.
For accessing data layer, I am using the following code:
public async Task<ExampleResult> GetExampleResultAsync(Parameter parameter, CancellationToken cancellationToken = default(CancellationToken))
{
GetCustomerResult result = null;
OracleConnection connection = this.Database.GetOracleConnection();
bool needClose = false;
if (connection.State != ConnectionState.Open)
{
await connection.OpenAsync(cancellationToken);
needClose = true;
}
try
{
using (OracleCommand cmd = connection.CreateCommand())
{
.... do the work
}
}
finally
{
if (needClose)
connection.Close();
}
return result;
}
however, this leads to concurrency and thus I am getting
the connection was not closed error.
The possible workaround for this that I ended up is to change the way of connecting to the database. I am thinking to use new connection instance for every request and surround these instances with using statement.
This would lead me lots of work and I would like to get an idea about best practices in the field for handling concurrency issues.
EDIT:
my caller function is below for your reference
public async Task<DomainResult<IList<MbRiskDto>>> QueryAsync(Action<MbrAccountAutoMatcherQueryParameter> parameter,
CancellationToken cancellationToken = default(CancellationToken))
{
parameter(_parameter);
var nonDeclaredMbrAccounts = await _nonDeclaredMbrAccountsQuery.QueryAsync(param => param.TransactionDate = _parameter.TransactionDate, cancellationToken);
if (nonDeclaredMbrAccounts.IsFailed)
nonDeclaredMbrAccounts.Errors.ForEach(error => _errors.Add(error));
var taskList = new List<Task<MbRiskDto>>();
var throttler = new SemaphoreSlim(initialCount: 10);
foreach (var nonDeclaredAccount in nonDeclaredMbrAccounts.Result)
{
await throttler.WaitAsync(cancellationToken);
var account = (AccountDto) nonDeclaredAccount.Clone();
var firstAccountHolder = Convert.ToInt32(account.AccountHolders.FirstOrDefault());
var task = Task.Run(async () =>
{
MbRiskDto result;
try
{
result = new MbRiskDto
{
KimNo = await GetKimNo(firstAccountHolder),
HesNo = account.AccountNo,
FinCode = await GetFinanceCode(account, firstAccountHolder, cancellationToken),
Unvan = account.SMA.Substring(0, Math.Min(account.SMA.Length, 54))
};
}
finally
{
throttler.Release();
}
return result;
}, cancellationToken);
taskList.Add(task);
}
var taskResult = await Task.WhenAll(taskList);
return DomainResult<IList<MbRiskDto>>.Success(taskResult);
}

According to the comments I removed Task.Run() clause and replaced with the code below. Right now, I believe that my concurrency problems are solved.
Func<Task<MbRiskDto>> mbRiskTask = async () => new MbRiskDto
{
KimNo = await GetKimNo(firstAccountHolder, cancellationToken),
HesNo = account.AccountNo,
FinCode = await GetFinanceCode(account, firstAccountHolder, cancellationToken),
Unvan = account.SMA.Substring(0, Math.Min(account.SMA.Length, 54))
};
taskList.Add(mbRiskTask.Invoke());
Thus, Resulting code can be seen below
public async Task<DomainResult<IList<MbRiskDto>>> QueryAsync(Action<MbrAccountAutoMatcherQueryParameter> parameter,
CancellationToken cancellationToken = default(CancellationToken))
{
parameter(_parameter);
var nonDeclaredMbrAccounts = await _nonDeclaredMbrAccountsQuery.QueryAsync(param => param.TransactionDate = _parameter.TransactionDate, cancellationToken);
if (nonDeclaredMbrAccounts.IsFailed)
nonDeclaredMbrAccounts.Errors.ForEach(error => _errors.Add(error));
var taskList = new List<Task<MbRiskDto>>();
foreach (var nonDeclaredAccount in nonDeclaredMbrAccounts.Result)
{
var account = (AccountDto) nonDeclaredAccount.Clone();
var firstAccountHolder = Convert.ToInt32(account.AccountHolders.FirstOrDefault());
Func<Task<MbRiskDto>> mbRiskTask = async () => new MbRiskDto
{
KimNo = await GetKimNo(firstAccountHolder, cancellationToken),
HesNo = account.AccountNo,
FinCode = await GetFinanceCode(account, firstAccountHolder, cancellationToken),
Unvan = account.SMA.Substring(0, Math.Min(account.SMA.Length, 54))
};
//var task = Task.Run();
taskList.Add(mbRiskTask.Invoke());
}
var taskResult = await Task.WhenAll(taskList);
return DomainResult<IList<MbRiskDto>>.Success(taskResult);
}
EDIT:
With this refactoring, the operation takes approximately 4minutes to finish. Do you have any suggestion in order to make it work faster?

Related

Cancelling a task and starting a new identical one with an updated parameter

This question is kind of an extension to this one, I have updated my code below to reflect a task that runs continuously updating web clients using through a Websocket connection, the tasks checks for stock prices for a provided company (symbol) received in the Recv() method, when the user changes the company the Recv() method should cancel the running task (for the older company) and start a new one for the newly provided company/symbol.
The problem I'm having is that the line where await Task.Run(() => StockPricingAsync(jsonResult), priceToken.Token); in the Recv method gets executed doesn't cancel the existing task although there is a priceToken.Cancel just right before. Any idea how to get the running task cancelled and start the same one immediately?
I have the priceToken variable declared globally so it can be accessed from within the running task, but this does not work.
I also tried to assign the task to variable and make that variable = null but that did not cancel the task as well.
I'm also open to change the approach completely if I have to, all I need is a running task that provides pricing information for a passed parameter which gets changed by the client while the connection is still open.
CancellationTokenSource priceToken = new CancellationTokenSource();
public async Task GetDataAsync(WebSocket ws)
{
CancellationToken token = new();
try
{
var sendTask = Task.Run(() => Send(ws, token));
var recvTask = Task.Run(() => Recv(ws, token));
do
{
await Task.Delay(1000);
} while (ws.State == WebSocketState.Open);
}
finally
{
await ws.CloseAsync(WebSocketCloseStatus.Empty, "", token);
}
}
async Task Recv(WebSocket ws, CancellationToken token)
{
Console.WriteLine("Recv task started...");
var buffer = WebSocket.CreateClientBuffer(1024, 1024);
WebSocketReceiveResult taskResult;
while (ws.State == WebSocketState.Open)
{
string jsonResult = "";
do
{
taskResult = await ws.ReceiveAsync(buffer, token);
jsonResult += Encoding.UTF8.GetString(buffer.Array, 0, taskResult.Count);
} while (!taskResult.EndOfMessage);
if (!string.IsNullOrEmpty(jsonResult))
{
Console.WriteLine("Queueing {0}", jsonResult);
priceToken.Cancel()
priceToken = new CancellationTokenSource();
await Task.Run(() => StockPricingAsync(jsonResult), priceToken.Token);
}
}
Console.WriteLine("Recv task exiting...");
}
async static Task Send(WebSocket ws, CancellationToken token)
{
Console.WriteLine("Send task started...");
do
{
string sendMsg = sendQueue.Take();
Console.WriteLine("Sending {0}", sendMsg);
var sendMsgBytes = Encoding.UTF8.GetBytes(sendMsg);
ArraySegment<byte> segmentBuffer = new ArraySegment<byte>(sendMsgBytes, 0, sendMsgBytes.Length);
await ws.SendAsync(segmentBuffer, WebSocketMessageType.Text, true, token);
} while (ws.State == WebSocketState.Open);
Console.WriteLine("Send task exiting...");
}
public async Task StockPricingAsync(string symbol)
{
var lastUpdated = DateTime.MinValue;
double previousPrice = 0;
new StockService().GetStockPricing(symbol, true);
while (!priceToken.Token.IsCancellationRequested)
{
var price = new StockService().GetStockPricing(symbol, false);
if (price != null && lastUpdated != price.LastPriceDate)
{
lastUpdated = price.LastPriceDate;
if (price.LastPrice > previousPrice)
price.Tick = Stock.Tick.UpTick;
else if (price.LastPrice < previousPrice)
price.Tick = Stock.Tick.DownTick;
else
price.Tick = Stock.Tick.NoChange;
previousPrice = price.LastPrice;
var json = JsonConvert.SerializeObject(new ServerData(new KeyValuePair<string, object>("StockPricing", price), _eventMessage), _jsonSerializerSettings);
sendQueue.Add(json);
}
await Task.Delay(3000, this.priceToken.Token);
}
if (this.priceToken.Token.IsCancellationRequested)
this.priceToken.Token.ThrowIfCancellationRequested();
}

I don't understand where I missed async\await

My code returns Task<List<...>>.
There is a type conversion error from Task<List<...>> to List<...>.
Tell me, please, where I did not finish await ?
public async Task<List<DepartamentsResponse>> Handle(GetDepartmentsRequest token, CancellationToken cancellationToken)
{
var departments = await _departmentApiClient.GetReception(token.accessToken, OdpgDepartmentType.Reception);
var result = departments.ConvertAll(async d => new DepartamentsResponse
{
FederalDistrict = GetFederalDistrictCode(d.FederalRegion.DistrictCode),
SubjectName = d.Name,
Supervisor = GetDirector(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
ContactsSupervisor = GetContacts(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
Schedule = "C 9:00 18:00",
ReceptionContacts = await GetAddressAsync(d.Addresses.FirstOrDefault(d => d.AddressType == DepartmentAddressType.Reception), token)
});
return result;
}
private async Task<string> GetAddressAsync(DepartmentAddressDto? address, GetDepartmentsRequest token)
{
if (address != null)
{
var fullAddress = await _fiasApiClient.GetFullAddress(token.accessToken,
new ESOG.Fias.Api.Model.GetFullAddressRequest
{ BaseAddressId = address.FiasId, Building = address.Building, Room = address.Room });
//var res = JsonConvert.DeserializeObject<DepartmentAddress>(fullAddress);
return fullAddress;
}
return "";
}
GetFederalDistrictCode, GetDirector, GetContacts - these methods are not asynchronous
It should just return a List<>, not Task<List<>>
Your call to departments.ConvertAll is returning a List<Task<DepartamentsResponse>>, but you want a Task<List<DepartamentsResponse>>.
Look at the Task.WhenAll method for how you would convert a collection of Tasks to a single Task that returns a collection. Then await that single Task and build your final list.

AsyncEnumerator library not processing collection

I am using the AsyncEnumerator library by #Serge Semenov, and after getting the orders, creating the ConcurrentBag and setting count to 0, nothing happens - can anyone see what's missing here? It never hits the processedOrders.ToList() line when debugging.
private async Task<List<OrderDto>> ProcessOrderEvents(DateTime cob)
{
var orders = await _client.GetOrderEvents(cob);
var processedOrders = new ConcurrentBag<OrderDto>();
var count = 0;
await orders.ParallelForEachAsync(async order =>
{
var orderDto = new FidessaOrderDto {Cob = cob};
var orderId = order.Ids.PrimaryId;
Debug.WriteLine($"Processing OrderId: {orderId} #= {++count}");
var instrumentDetails = await GetInstrumentDetails(order);
if (instrumentDetails.Results.Any())
{
//...
}
var tradeDetails = await _client.GetTrade(tradeId: fill, cob);
processedOrders.Add(orderDto);
}, maxDegreeOfParallelism:2);
return processedOrders.ToList();
}
Update: Adding Test root.
[TestMethod]
public async Task Test_20_07_2022()
{
var service = new DataService(new ApiClient());
var data = await service.ProcessData(new DateTime(2022, 07, 20), ReportDataType.Orders);
Assert.AreEqual(920, data.Count);
}
public async Task<List<OrderDto>> ProcessData(DateTime cob,
ReportDataType dataType)
{
await PopulateRouteEvents(cob);
var reportData = new List<OrderDto>();
if (dataType == ReportDataType.Orders)
reportData = await ProcessOrderEventsTpl(cob);
return reportData;
}

Semaphore for limiting requests per second doesn't work

I'm using Google Analytics and that service has limit of 10 concurrent requests. I had to limit my API somehow, so I decided to use a semaphore, but it seems it doesn't work. All requests are triggered simultaneously. I can't find the problem in my code.
public async Task<SiteAnalyticsDTO> Handle(GetSiteAnalyticsParameter query)
{
var todayVisits = _googleAnalyticsService.GetTodayVisitsNumber();
var todayTraffic = _googleAnalyticsService.GetTodayTraffic();
var newAndReturningUsers = _googleAnalyticsService.GetNewAndReturningUsersNumber();
var averageSessionDuration = _googleAnalyticsService.GetAverageSessionDuration();
var deviceCategory = _googleAnalyticsService.GetSessionNumberByDeviceCategory();
var topPages = _googleAnalyticsService.GetTodaysTopPages();
var guestsAndRegisteredUsers = _googleAnalyticsService.GetGuestsVsRegisteredUsers();
var averageNumberOfSessionsPerDay = _googleAnalyticsService.GetAverageSessionsNumber();
var visitsPerWeekday = _googleAnalyticsService.GetTrafficByWeekday();
var visitsByHours = _googleAnalyticsService.GetTrafficByTimeOfDay();
var usersByPrefectures = _googleAnalyticsService.GetUsersByPrefectures();
var usersByCountry = _googleAnalyticsService.GetUsersByCountry();
var tasks = new List<Task>()
{
todayVisits, todayTraffic, newAndReturningUsers,
averageSessionDuration, deviceCategory, topPages,
guestsAndRegisteredUsers, averageNumberOfSessionsPerDay, visitsPerWeekday,
visitsByHours, usersByPrefectures, usersByCountry
};
var throttler = new SemaphoreSlim(MaxRequests, MaxRequests);
foreach(var task in tasks)
{
await throttler.WaitAsync();
try
{
await task;
await Task.Delay(1000); // It's important due to limits of Google Analytics requests (10 queries per second per IP address)
}
finally
{
throttler.Release();
}
}
await Task.WhenAll(tasks);
return new SiteAnalyticsDTO()
{
TodayVisits = await todayVisits,
TodayTraffic = await todayTraffic,
NewAndReturningUsers = await newAndReturningUsers,
AverageSessionDuration = await averageSessionDuration,
DeviceCategory = await deviceCategory,
TopPages = await topPages,
GuestsAndRegisteredUsers = await guestsAndRegisteredUsers,
AverageNumberOfSessionsPerDay = await averageNumberOfSessionsPerDay,
VisitsPerWeekday = await visitsPerWeekday,
VisitsByHours = await visitsByHours,
UsersByPrefectures = await usersByPrefectures,
UsersByCountry = await usersByCountry
};
}
And here is some example method for Google Analytics call:
public async Task<int> GetTodayVisitsNumber(List<long> listingIds = null)
{
string filter = GetFilter(listingIds);
var getReportsRequest = GetReportsRequestModel(GetTodayDateRange(), "ga:sessionCount", "ga:sessions", _configuration.MainViewId, filter);
var response = await _service.Reports.BatchGet(getReportsRequest).ExecuteAsync();
Console.WriteLine(response);
var data = response.Reports.FirstOrDefault();
return Convert.ToInt32(data?.Data.Totals[0].Values[0]);
}
All requests are triggered simultaneously.
Let's take a look here
var todayVisits = _googleAnalyticsService.GetTodayVisitsNumber();
var todayTraffic = _googleAnalyticsService.GetTodayTraffic();
var newAndReturningUsers = _googleAnalyticsService.GetNewAndReturningUsersNumber();
var averageSessionDuration = _googleAnalyticsService.GetAverageSessionDuration();
var deviceCategory = _googleAnalyticsService.GetSessionNumberByDeviceCategory();
var topPages = _googleAnalyticsService.GetTodaysTopPages();
var guestsAndRegisteredUsers = _googleAnalyticsService.GetGuestsVsRegisteredUsers();
var averageNumberOfSessionsPerDay = _googleAnalyticsService.GetAverageSessionsNumber();
var visitsPerWeekday = _googleAnalyticsService.GetTrafficByWeekday();
var visitsByHours = _googleAnalyticsService.GetTrafficByTimeOfDay();
var usersByPrefectures = _googleAnalyticsService.GetUsersByPrefectures();
var usersByCountry = _googleAnalyticsService.GetUsersByCountry();
You are storing the result of each of these methods. When you use the parenthesis tokens such as "methodName();" you invoke the method and store the result in var.
You then store the results of these methods in a list, then await each of them with a Semaphore to limit the number of tasks that can be awaited at once.
The issue is: each await finishes immediately because you already waited(synchronously) them when you initially invoked them above.
This leads you to believe that the SemaphoreSlim isn't working, because if each Task immediately returns when awaited(because they've already been invoked) then there is no time between them.
Store the async methods for later instead of invoking them all at once.
You can't store a delegate like those in a var you have to store them in an explicitly typed variable Func<TResult>.
For example:
Func<Task<object>> todayVisits = _googleAnalyticsService.GetTodayVisitsNumber;
editors note, I have no clue what these methods return I substituted object to be as general as possible
Now - it would be pretty cumbersome if we stored each one in a variable, so instead of storing them in individual variables let's just put them directly in a list like this:
var awaitableTasks = new List<Func<Task<object>>>()
{
_googleAnalyticsService.GetTodayVisitsNumber,
_googleAnalyticsService.GetTodayTraffic,
_googleAnalyticsService.GetNewAndReturningUsersNumber,
_googleAnalyticsService.GetAverageSessionDuration,
_googleAnalyticsService.GetSessionNumberByDeviceCategory,
_googleAnalyticsService.GetTodaysTopPages,
_googleAnalyticsService.GetGuestsVsRegisteredUsers,
_googleAnalyticsService.GetAverageSessionsNumber,
_googleAnalyticsService.GetTrafficByWeekday,
_googleAnalyticsService.GetTrafficByTimeOfDay,
_googleAnalyticsService.GetUsersByPrefectures,
_googleAnalyticsService.GetUsersByCountry
};
Because these new objects themselves aren't tasks but rather methods that return a Task we have to change how we store and invoke them, for this we'll use a local method so I'll go over each of the changes I made.
Let's create that Semaphore and create somewhere we can put tasks to keep track of them.
Let's also create somewhere we can store the results of each of those tasks when we await them.
var throttler = new SemaphoreSlim(MaxRequests, MaxRequests);
var tasks = new List<Task>();
ConcurrentDictionary<string, object> results = new();
Let's create a local method with a couple of responsibilities
Accept a Func<Task<object>> as a param
Await the method
Put the result of that method somewhere we can get it later
Release the Semphore even if it encounters an error
async Task Worker(Func<Task<object>> awaitableFunc)
{
try
{
resultDict.TryAdd(awaitableFunc.GetMethodInfo().Name, await awaitableFunc());
}
finally
{
throttler.Release();
}
}
Editors note: You can pull the same thing off with a lambda expression, but I prefer to use a local method for clarity and formatting.
Start the workers and store the tasks they return.
That way.. if they aren't done by the time the last couple are created, then we can wait for them to finish before creating the final object(since we are going to need all of the results they provide to create the final object).
foreach (var task in awaitableTasks)
{
await throttler.WaitAsync();
tasks.Add(Task.Run(() => Worker(task)));
}
// wait for the tasks to finish
await Task.WhenAll(tasks);
Create the final object then return it.
return new SiteAnalyticsDTO()
{
TodayVisits = resultDict[nameof(_googleAnalyticsService.GetTodayVisitsNumber)],
TodayTraffic = resultDict[nameof(_googleAnalyticsService.GetTodayTraffic)],
NewAndReturningUsers = resultDict[nameof(_googleAnalyticsService.GetNewAndReturningUsersNumber)],
AverageSessionDuration = resultDict[nameof(_googleAnalyticsService.GetAverageSessionDuration)],
DeviceCategory = resultDict[nameof(_googleAnalyticsService.GetSessionNumberByDeviceCategory)],
TopPages = resultDict[nameof(_googleAnalyticsService.GetTodaysTopPages)],
GuestsAndRegisteredUsers = resultDict[nameof(_googleAnalyticsService.GetGuestsVsRegisteredUsers)],
AverageNumberOfSessionsPerDay = resultDict[nameof(_googleAnalyticsService.GetAverageSessionsNumber)],
VisitsPerWeekday = resultDict[nameof(_googleAnalyticsService.GetTrafficByWeekday)],
VisitsByHours = resultDict[nameof(_googleAnalyticsService.GetTrafficByTimeOfDay)],
UsersByPrefectures = resultDict[nameof(_googleAnalyticsService.GetUsersByPrefectures)],
UsersByCountry = resultDict[nameof(_googleAnalyticsService.GetUsersByCountry)]
};
Wrap it all together and I think we have something that might work, or at least be easy to modify to meet your needs.
public static async Task<SiteAnalyticsDTO> Handle(GetSiteAnalyticsParameter query)
{
// store these methods so we can iterate and execute them later
var awaitableTasks = new List<Func<Task<object>>>()
{
_googleAnalyticsService.GetTodayVisitsNumber,
_googleAnalyticsService.GetTodayTraffic,
_googleAnalyticsService.GetNewAndReturningUsersNumber,
_googleAnalyticsService.GetAverageSessionDuration,
_googleAnalyticsService.GetSessionNumberByDeviceCategory,
_googleAnalyticsService.GetTodaysTopPages,
_googleAnalyticsService.GetGuestsVsRegisteredUsers,
_googleAnalyticsService.GetAverageSessionsNumber,
_googleAnalyticsService.GetTrafficByWeekday,
_googleAnalyticsService.GetTrafficByTimeOfDay,
_googleAnalyticsService.GetUsersByPrefectures,
_googleAnalyticsService.GetUsersByCountry
};
// create a way to limit the number of concurrent requests
var throttler = new SemaphoreSlim(MaxRequests, MaxRequests);
// create a place to store the tasks we create
var finalTasks = new List<Task>();
// make sure we have some where to put our results
ConcurrentDictionary<string, object> resultDict = new();
// make a worker that accepts one of those methods, invokes it
// then adds the result to the dict
async Task Worker(Func<Task<object>> awaitableFunc)
{
try
{
resultDict.TryAdd(awaitableFunc.GetMethodInfo().Name, await awaitableFunc());
}
finally
{
// make sure even if we encounter an error we still release the semphore
throttler.Release();
}
}
// iterate over the tasks, wait for the sempahore
// when we get a slot, create a worker and send it to the background
foreach (var task in awaitableTasks)
{
await throttler.WaitAsync();
finalTasks.Add(Task.Run(() => Worker(task)));
}
// wait for any remaining tasks to finish up in the background if they are still running
await Task.WhenAll(finalTasks);
// create the return object from the results of the dictionary
return new SiteAnalyticsDTO()
{
TodayVisits = resultDict[nameof(_googleAnalyticsService.GetTodayVisitsNumber)],
TodayTraffic = resultDict[nameof(_googleAnalyticsService.GetTodayTraffic)],
NewAndReturningUsers = resultDict[nameof(_googleAnalyticsService.GetNewAndReturningUsersNumber)],
AverageSessionDuration = resultDict[nameof(_googleAnalyticsService.GetAverageSessionDuration)],
DeviceCategory = resultDict[nameof(_googleAnalyticsService.GetSessionNumberByDeviceCategory)],
TopPages = resultDict[nameof(_googleAnalyticsService.GetTodaysTopPages)],
GuestsAndRegisteredUsers = resultDict[nameof(_googleAnalyticsService.GetGuestsVsRegisteredUsers)],
AverageNumberOfSessionsPerDay = resultDict[nameof(_googleAnalyticsService.GetAverageSessionsNumber)],
VisitsPerWeekday = resultDict[nameof(_googleAnalyticsService.GetTrafficByWeekday)],
VisitsByHours = resultDict[nameof(_googleAnalyticsService.GetTrafficByTimeOfDay)],
UsersByPrefectures = resultDict[nameof(_googleAnalyticsService.GetUsersByPrefectures)],
UsersByCountry = resultDict[nameof(_googleAnalyticsService.GetUsersByCountry)]
};
}
The problem of your setup is that all tasks are started at the same time, and only their awaiting is throttled. Throttling the awaiting has no useful effect. Only your continuations are delayed. The target service receives all the requests in bulk.
My suggestion is to use a dedicated class to encapsulate the throttling logic. It seems that you need to limit both the concurrency and the rate of sending the requests, and each one of these limitations can be achieved by using a separate SemaphoreSlim. Here is a simple implementation:
public class ThrottledExecution
{
private readonly SemaphoreSlim _concurrencySemaphore;
private readonly SemaphoreSlim _delaySemaphore;
private readonly TimeSpan _delay;
public ThrottledExecution(int concurrencyLimit, TimeSpan rateLimitTime,
int rateLimitCount)
{
// Arguments validation omitted
_concurrencySemaphore = new SemaphoreSlim(concurrencyLimit, concurrencyLimit);
_delaySemaphore = new SemaphoreSlim(rateLimitCount, rateLimitCount);
_delay = rateLimitTime;
}
public async Task<TResult> Run<TResult>(Func<Task<TResult>> action)
{
await _delaySemaphore.WaitAsync();
ScheduleDelaySemaphoreRelease();
await _concurrencySemaphore.WaitAsync();
try { return await action().ConfigureAwait(false); }
finally { _concurrencySemaphore.Release(); }
}
private async void ScheduleDelaySemaphoreRelease()
{
await Task.Delay(_delay).ConfigureAwait(false);
_delaySemaphore.Release();
}
}
And here is how you could use it:
public async Task<SiteAnalyticsDTO> Handle(GetSiteAnalyticsParameter query)
{
var throttler = new ThrottledExecution(MaxRequests, TimeSpan.FromSeconds(1), 1);
var todayVisits = throttler.Run(() => _service.GetTodayVisitsNumber());
var todayTraffic = throttler.Run(() => _service.GetTodayTraffic());
var newAndReturningUsers = throttler.Run(() => _service.GetNewAndReturningUsersNumber());
var averageSessionDuration = throttler.Run(() => _service.GetAverageSessionDuration());
var deviceCategory = throttler.Run(() => _service.GetSessionNumberByDeviceCategory());
var topPages = throttler.Run(() => _service.GetTodaysTopPages());
var guestsAndRegisteredUsers = throttler.Run(() => _service.GetGuestsVsRegisteredUsers());
var averageNumberOfSessionsPerDay = throttler.Run(() => _service.GetAverageSessionsNumber());
var visitsPerWeekday = throttler.Run(() => _service.GetTrafficByWeekday());
var visitsByHours = throttler.Run(() => _service.GetTrafficByTimeOfDay());
var usersByPrefectures = throttler.Run(() => _service.GetUsersByPrefectures());
var usersByCountry = throttler.Run(() => _service.GetUsersByCountry());
var tasks = new List<Task>()
{
todayVisits, todayTraffic, newAndReturningUsers,
averageSessionDuration, deviceCategory, topPages,
guestsAndRegisteredUsers, averageNumberOfSessionsPerDay, visitsPerWeekday,
visitsByHours, usersByPrefectures, usersByCountry
};
await Task.WhenAll(tasks);
return new SiteAnalyticsDTO()
{
TodayVisits = await todayVisits,
TodayTraffic = await todayTraffic,
NewAndReturningUsers = await newAndReturningUsers,
AverageSessionDuration = await averageSessionDuration,
DeviceCategory = await deviceCategory,
TopPages = await topPages,
GuestsAndRegisteredUsers = await guestsAndRegisteredUsers,
AverageNumberOfSessionsPerDay = await averageNumberOfSessionsPerDay,
VisitsPerWeekday = await visitsPerWeekday,
VisitsByHours = await visitsByHours,
UsersByPrefectures = await usersByPrefectures,
UsersByCountry = await usersByCountry,
};
}
It seems that a partially successful result is not useful to you, so you could consider adding some auto-cancellation logic inside the ThrottledExecution class. In case a task fails, all pending and subsequent asynchronous operations should be canceled.

Ping ASync Problems

I have to have a breakpoint on the indicated line below for the following code to work. Otherwise, the program just pauses indefinitely.
async Task<List<PingReply>> PingAsync()
{
var pingTargetHosts = GetIPs();
var pingTasks = pingTargetHosts.Select(host => new Ping().SendPingAsync(host, 2000)).ToList();
var pingResults = await Task.WhenAll(pingTasks); //THIS LINE NEEDS A BREAKPOINT TO WORK
return pingResults.ToList();
}
The code is called like this
List<PingReply> GetReplies()
{
var PingIPs = PingAsync();
MessageBox.Show("Loading:...");
List<PingReply> Results = PingIPs.Result;
return Results;
}
Could anyone tell me how I need to amend my code in order to remove the breakpoint but still have a functional piece of code.
EDIT:
Not tested, but 99% sure this will work.
async Task<List<PingReply>> PingAsync()
{
var pingTargetHosts = GetIPs();
var pingTasks = pingTargetHosts.Select(async host => await new Ping().SendPingAsync(host, 2000)).ToList();
var pingResults = await Task.WhenAll(pingTasks);
return pingResults.ToList();
}
async Task<List<PingReply>> GetReplies()
{
var PingIPs = PingAsync();
MessageBox.Show("Loading:...");
return await PingIPs;
}
async Task BuildDictionary()
{
List<PingReply> Replies = await GetReplies();
//Use this list via foreach
}
async private void button1_Click(object sender, EventArgs e)
{
EthernetCheck checker = new EthernetCheck();
checker.Check();
bool IsEthernetIn = checker.PluggedIn;
if (IsEthernetIn)
{
await BuildDictionary();
//Do Stuff
}
}
Your code is deadlocking because you're blocking on asynchronous code. To fix it, use async all the way:
async Task<List<PingReply>> GetRepliesAsync()
{
var PingIPs = PingAsync();
MessageBox.Show("Loading:...");
return await PingIPs;
}
Usage:
var replies = await GetRepliesAsync();
When use async/await you should remember that .net framework will control the flow of program execution so, I recommend u to make all calls asynchronous to avoid this kind of problem.
async Task<List<PingReply>> PingAsync()
{
var pingTargetHosts = await GetIPs();
var pingTasks = pingTargetHosts.Select(host => await new Ping().SendPingAsync(host, 2000)).ToList();
var pingResults = await Task.WhenAll(pingTasks);
return pingResults.ToList();
}

Categories

Resources