Using TPL Dataflow, how to compose workflow with BatchBlock in the middle and back out to individual item block? - task-parallel-library

The idea is to process a list of items, individually and then batch up and then go back to processing individually seamlessly.
In the batch processing block, I may be querying or saving to the database. Hitting the DB with a batch is a lot more efficient than hitting it multiple times per each item in the list.
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace DataflowTest
{
class Program
{
static async Task Main(string[] args)
{
var execOptions = new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded };
var block1 = new TransformBlock<WorkItem, WorkItem>(async item =>
{
// perform work on individual item
await Task.Delay(1000);
Console.WriteLine($"Block 1 - Item {item.Id}");
return item;
}, execOptions);
var block2 = new TransformBlock<WorkItem, WorkItem>(async item =>
{
// perform more work on individual item
await Task.Delay(1000);
Console.WriteLine($"Block 2 - Item {item.Id}");
return item;
}, execOptions);
var batch = new BatchBlock<WorkItem>(5);
var batchWork = new ActionBlock<WorkItem[]>(async items =>
{
Console.WriteLine($"batchWork - {items.Length} Items");
// perform batch work - query database, etc.
await Task.Delay(2000);
await Task.WhenAll(items.Select(x => block2.SendAsync(x)));
}, execOptions);
var batch2 = new BatchBlock<WorkItem>(10);
var save = new ActionBlock<WorkItem[]>(async items =>
{
Console.WriteLine($"save - {items.Length} Items");
// save items to the DB
await Task.Delay(2000);
}, execOptions);
var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };
block1.LinkTo(batch, linkOptions);
batch.LinkTo(batchWork, linkOptions);
block2.LinkTo(batch2, linkOptions);
batch2.LinkTo(save, linkOptions);
Console.WriteLine("Starting work");
var workItems = Enumerable.Range(1, 10).Select(x => new WorkItem { Id = x }).ToArray();
await Task.WhenAll(workItems.Select(x => block1.SendAsync(x)));
block1.Complete();
await batchWork.Completion;
block2.Complete();
await save.Completion;
Console.WriteLine("All Done");
Console.WriteLine("Hit Enter");
Console.ReadLine();
}
}
class WorkItem
{
public int Id { get; set; }
}
}
I am looking for some feedback. Basically the above code sample seems to work.
The critical piece of code is within "batchWork" where I am queueing up to "block2" by calling SendAsync on each item. I don't know of a way to link up any other way. Perhaps there is a better approach to what I am trying to accomplish here.
Any suggestions?

You don't need to use SendAsync. You can change batchWork to a TransformManyBlock and connect it to the next block:
var batchWork = new TransformManyBlock<WorkItem[],WorkItem>(async items =>
{
Console.WriteLine($"batchWork - {items.Length} Items");
// perform batch work - query database, etc.
return items;
}, execOptions);
....
batch.LinkTo(batchWork, linkOptions);
batchWork.LinkTo(block2, linkOptions);
block2.LinkTo(batch2, linkOptions);

Related

Trying to create the database using ABP framework while DB Migration

I have inserted connectionstring in "[AbpTenantConnectionStrings]" table for same tenant id.
My objective is to create both the DB and migrage the DB
DB
while running below code only "Default Db is creating", can anyone let me know how to create and migrate other DB as well. what changes need to be done in the below code.
var tenants = await _tenantRepository.GetListAsync(includeDetails: true);
var migratedDatabaseSchemas = new HashSet<string>();
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
if (tenant.ConnectionStrings.Any())
{
var tenantConnectionStrings = tenant.ConnectionStrings
.Select(x => x.Value)
.ToList();
if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings))
{
await MigrateDatabaseSchemaAsync(tenant);
migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings);
}
}
await SeedDataAsync(tenant);
}
After the below changes able to migrate or create the Db based on the connection-string inserted in "AbpTenantConnectionStrings":
public async Task MigrateAsync()
{
var initialMigrationAdded = AddInitialMigrationIfNotExist();
//var str = (await _connectionResolver.ResolveAsync("Default1"));
if (initialMigrationAdded)
{
return;
}
Logger.LogInformation("Started database migrations...");
await MigrateDatabaseSchemaAsync();
await SeedDataAsync();
Logger.LogInformation($"Successfully completed host database migrations.");
var tenants = await _tenantRepository.GetListAsync(includeDetails: true);
var migratedDatabaseSchemas = new HashSet<string>();
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
if (tenant.ConnectionStrings.Any())
{
var tenantConnectionStrings = tenant.ConnectionStrings
.Select(x => x.Value)
.ToList();
tenantConnectionStrings.ForEach(async s =>
{
await MigrateDatabaseSchemaAsync(s);
// migratedDatabaseSchemas.AddIfNotContains(s);
});
//if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings))
//{
// await MigrateDatabaseSchemaAsync(tenant);
// migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings);
//}
}
await SeedDataAsync(tenant);
}
Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations.");
}
Logger.LogInformation("Successfully completed all database migrations.");
Logger.LogInformation("You can safely end this process...");
}
private async Task MigrateDatabaseSchemaAsync(string str)
{
//Logger.LogInformation(
// $"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database...");
foreach (var migrator in _dbSchemaMigrators)
{
await migrator.MigrateAsync(str);
}
}
public interface IBookStoreDbSchemaMigrator
{
Task MigrateAsync();
Task MigrateAsync(string con);
}
public class EntityFrameworkCoreBookStoreDbSchemaMigrator
: IBookStoreDbSchemaMigrator, ITransientDependency
{
private readonly IServiceProvider _serviceProvider;
public EntityFrameworkCoreBookStoreDbSchemaMigrator(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task MigrateAsync()
{
/* We intentionally resolving the BookStoreMigrationsDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.
*/
await _serviceProvider.GetRequiredService<BookStoreMigrationsDbContext>()
.Database
.MigrateAsync();
}
public async Task MigrateAsync(string con)
{
var context = _serviceProvider
.GetRequiredService<BookStoreMigrationsDbContext>();
context.Database.SetConnectionString(con);
context.Database.Migrate();
}
}
Let me know if anyone has suggestions or a better approach.

Parallel call to Elasticsearch in c#

I am trying to make a parallel call to Elasticsearch index for multiple queries and aggregate the result, using Parallel.ForEach.
My code:
private static List<SearchResponse<dynamic>> SearchQueryInParallel(string indexName, string lang, string[] eQueries)
{
var result = new List<SearchResponse<dynamic>>();
var exceptions = new ConcurrentQueue<Exception>();
object mutex = new object();
try
{
Parallel.ForEach(eQueries,
() => new SearchResponse<dynamic>()
, (q, loopState, subList) =>
{
var x = LowlevelClient.Search<SearchResponse<dynamic>>(indexName, $"article_{lang}", q);
subList = x;
return subList;
}, subList =>
{
lock (result)
result.Add(subList);
}
);
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
{
exceptions.Enqueue(e);
}
}
if (exceptions.ToList().Any())
{
//there are exceptions, do something with them
//do something?
}
return result;
}
The problem I am facing is that sublist in the above case is long.
It gives me the following error:
Can not convert from SearchResponse to long.
The same thing is working when I used without multithreading, the code is:
var items = new List<dynamic>();
var searchResponse = lowLevelClient.Search<SearchResponse<dynamic>>(elasticIndexName, $"article_{languageCode.ToLowerInvariant()}", query);
foreach (var document in searchResponse.Body.Documents)
{
items.Add(document);
}
Any help, please? If somebody has any other way to achieve the parallel call and aggregating data from returned values would be greatly appreciated.

Initialize an xamarin view after an async method

Good evening everyone.
For some time now I have been to Xamarin. My first tests are rather conclusive. I decided to try to make a small application that retrieves information in a database via an API and then update this data via a ListView.
When I launch the application on my emulator everything works but as soon as I install the application on my phone it crashes. I thought this was because the API but I have an API that I use to check the Login / password that works correctly.
The API that returns the data reviews a lot of line about 3500/4000, can this be the reason?
So I passed the loading of the data in my viewModel in an async method but the problem now is that the view loads before the data is loaded correctly. Is there a way to get the view initialized after the data is loaded?
Below my code.
Initializing my viewModel
class ManageInventViewModel
{
public ObservableCollection<InventViewModel> InventProduct { get; set; }
public Command<InventViewModel> UpdateCommand
{
get
{
return new Command<InventViewModel>(invent =>
{
var index = invent.IndexLigneInventaire;
InventProduct.Remove(invent);
InventProduct.Insert(index, invent);
});
}
}
public Command<InventViewModel> ResetStock
{
get
{
return new Command<InventViewModel>(invent =>
{
var index = InventProduct.IndexOf(invent);
InventProduct.Remove(invent);
invent.RealStockProduct = 0;
InventProduct.Insert(index, invent);
});
}
}
public ManageInventViewModel()
{
LoadInventaire();
}
private async void LoadInventaire()
{
var listMvt = await Utils.Utils.GetListMouvementUntilDate();
var listStock = Utils.Utils.GetStockByProduct(listMvt).Take(20);
InventProduct = new ObservableCollection<InventViewModel>();
var indexLine = 0;
foreach (var stock in listStock)
{
var inventViewModel = new InventViewModel
{
LibelleProduit = stock.LibelleProduit,
PrCodeProduit = stock.PrCodeProduit,
UpCodeProduit = stock.UpCodeProduit,
RealStockProduct = stock.StockTheoProdct,
StockTheoProdct = stock.StockTheoProdct,
IndexLigneInventaire = indexLine
};
++indexLine;
InventProduct.Add(inventViewModel);
}
}
}
Initializinz my view
public partial class InventPage : ContentPage
{
public InventPage()
{
InitializeComponent();
TableInvent.ItemSelected += (sender, e) =>
{
if (TableInvent.SelectedItem != null)
{
if (TableInvent.SelectedItem is InventViewModel item)
{
PopupNavigation.Instance.PushAsync(new ChangeStockModal(item, this));
}
TableInvent.SelectedItem = null;
}
};
}
private void Reset_Stock(object sender, EventArgs e)
{
var input = sender as Button;
var inventViewModel = input?.BindingContext as InventViewModel;
var listViewModel = BindingContext as ManageInventViewModel;
listViewModel?.ResetStock.Execute(inventViewModel);
}
public void Update_Stock_List(InventViewModel dataStockUpdate)
{
var listViewModel = BindingContext as ManageInventViewModel;
listViewModel?.UpdateCommand.Execute(dataStockUpdate);
PopupNavigation.Instance.PopAsync();
}
}
Thanks
I managed to create the ActivityIndicator but I can not get my data loaded while I'm displaying the wait screen.
Regarding this issue, I don't see you useActivityIndicator from your code,maybe you didn't update your code, I think if you use useActivityIndicator , You can bind one property to ActivityIndicator IsRunning and IsVisible, then you can solve your issue.
Related use ActivityIndicator step, you can take a look:
ActivityIndicator

Await Async : how to get one method completed before the second one starts

How to I make the call so that the GetRecords is completed before _reportViewerService.ShowReport starts. Using SignalR the setUi updates a txt field which displays names from part of the result calculated in getRecords, and the rest should be printed in report there after.
(whats happening now is both running the same time, then the report being showed before I see the live update status)
Thanks in advance
public async Task ViewReport()
{
var reportData = await _apiCallExecutor.ExecuteAsync(new GetRecords(queryModel, setUiHooks));
try
{
if (reportData.Count > 0)
{
var settings = new ReportSettings();
settings.ReportPath = "Utilities/SetDeliveryIdByBatchReport";
settings.ReportTitle = "Set Delivery ID By Batch - Exception Listing";
settings.DataSources.Add("DeliveryIdExceptionRecords", reportData);
ReportStatus = "Printing Exception Report...";
await _reportViewerService.ShowReport(settings);
}
}
finally
{
ViewModelState = ViewModelStates.Edit;
}
ReportStatus = "Done...";
}
You want to use some kind of "signal" from GetRecords, e.g., an IObservable or Task that completes when the data has arrived.
class GetRecords
{
...
public Task Done { get; }
// or: public IObservable<Unit> Done { get; }
}
then:
var getRecords = new GetRecords(queryModel, setUiHooks);
var reportData = await _apiCallExecutor.ExecuteAsync(getRecords);
await getRecords.Done;
...

Integrating AspNet.WebApi with AspNet.SignalR

I want to integrate Microsoft.AspNet.SignalR version="2.1.2" with Microsoft.AspNet.WebApi version="5.2.2" so that the API can communicate in REAL-TIME. I found one VERY NICE SAMPLE that implements/works exactly the same way I want, but the sample uses jquery.signalR-0.5.0.js. Some of the earlier implementations have changed, and so far here is what I have done in a failed effort to upgrade the solution to use the latest signalr, asp.net web api and owin.
I left the Hub as it is
using SignalR.Hubs;
namespace NdcDemo.Hubs
{
// This hub has no inbound APIs, since all inbound communication is done
// via the HTTP API. It's here for clients which want to get continuous
// notification of changes to the ToDo database.
[HubName("todo")]
public class ToDoHub : Hub { }
}
I also left the ApiControllerWithHub class as it is
using System;
using System.Web.Http;
using SignalR;
using SignalR.Hubs;
namespace NdcDemo.Controllers
{
public abstract class ApiControllerWithHub<THub> : ApiController
where THub : IHub
{
Lazy<IHubContext> hub = new Lazy<IHubContext>(
() => GlobalHost.ConnectionManager.GetHubContext<THub>()
);
protected IHubContext Hub
{
get { return hub.Value; }
}
}
}
For the ToDoController, I changed the
Hub.Clients.addItem(item), Hub.Clients.updateItem(toUpdate),
Hub.Clients.deleteItem(id)
to
Hub.Clients.All.addItem(item), Hub.Clients.All.updateItem(toUpdate),
Hub.Clients.All.deleteItem(id)
and this is now the full ToDoController class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Web.Http;
using NdcDemo.Hubs;
using NdcDemo.Models;
namespace NdcDemo.Controllers
{
[InvalidModelStateFilter]
public class ToDoController : ApiControllerWithHub<ToDoHub>
{
private static List<ToDoItem> db = new List<ToDoItem>
{
new ToDoItem { ID = 0, Title = "Do a silly demo on-stage at NDC" },
new ToDoItem { ID = 1, Title = "Wash the car" },
new ToDoItem { ID = 2, Title = "Get a haircut", Finished = true }
};
private static int lastId = db.Max(tdi => tdi.ID);
public IEnumerable<ToDoItem> GetToDoItems()
{
lock (db)
return db.ToArray();
}
public ToDoItem GetToDoItem(int id)
{
lock (db)
{
var item = db.SingleOrDefault(i => i.ID == id);
if (item == null)
throw new HttpResponseException(
Request.CreateResponse(HttpStatusCode.NotFound)
);
return item;
}
}
public HttpResponseMessage PostNewToDoItem(ToDoItem item)
{
lock (db)
{
// Add item to the "database"
item.ID = Interlocked.Increment(ref lastId);
db.Add(item);
// Notify the connected clients
Hub.Clients.addItem(item);
// Return the new item, inside a 201 response
var response = Request.CreateResponse(HttpStatusCode.Created, item);
string link = Url.Link("apiRoute", new { controller = "todo", id = item.ID });
response.Headers.Location = new Uri(link);
return response;
}
}
public ToDoItem PutUpdatedToDoItem(int id, ToDoItem item)
{
lock (db)
{
// Find the existing item
var toUpdate = db.SingleOrDefault(i => i.ID == id);
if (toUpdate == null)
throw new HttpResponseException(
Request.CreateResponse(HttpStatusCode.NotFound)
);
// Update the editable fields and save back to the "database"
toUpdate.Title = item.Title;
toUpdate.Finished = item.Finished;
// Notify the connected clients
Hub.Clients.updateItem(toUpdate);
// Return the updated item
return toUpdate;
}
}
public HttpResponseMessage DeleteToDoItem(int id)
{
lock (db)
{
int removeCount = db.RemoveAll(i => i.ID == id);
if (removeCount <= 0)
return Request.CreateResponse(HttpStatusCode.NotFound);
// Notify the connected clients
Hub.Clients.deleteItem(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
}
}
And then I put app.MapSignalR();
But the demo doesn't work...the API doesn't contact clients...Where am I going wrong?
I would still appreciate any more simpler recommendations based on this solution.
Solution from OP.
Answer:
After a a cup of camomile tea, i found out that the clients had to include the KEYWORD CLIENT before the dynamic methods in Todo.js... So, here is what that needs to be modified so that the sample works
hub.client.addItem = function (item) {
alert("i just received something...");
viewModel.add(item.ID, item.Title, item.Finished);
};
hub.client.deleteItem = function (id) {
viewModel.remove(id);
};
hub.client.updateItem = function (item) {
viewModel.update(item.ID, item.Title, item.Finished);
};
And it works!

Resources