简体   繁体   中英

Mixing async tasks with blocking sync task

I am writing a set of async tasks that go away an download and parse data, however I am running in to a bit of a blank with the next step where I am updating a database.

The issue is that for the sake of performance I am using a TableLock to load rather large datasets, so what I am wanting to do is have my import service wait for the first Task to return, start the import. Should another Task complete while the first import is running the process joins a queue and waits for the import service is complete for task 1.

eg.

Async - Task1 - Task2 - Task3

Sync - ImportService

RunAsync Tasks

Task3 returns first > ImportService.Import(Task3)
Task1 return, ImportService is still running. Wait()
ImportService.Complete() event
Task2 returns. Wait()
ImportService.Import(Task1)
ImportService.Complete() event
ImportService.Import(Task2)
ImportService.Complete() event

Hope this makes sense!

You can't really use await here, but you can wait on multiple tasks to complete:

var tasks = new List<Task)();
// start the tasks however
tasks.Add(Task.Run(Task1Function);
tasks.Add(Task.Run(Task2Function);
tasks.Add(Task.Run(Task2Function);

while (tasks.Count > 0)
{
   var i = Task.WaitAny(tasks.ToArray()); // yes this is ugly but an array is required
   var task = tasks[i];
   tasks.RemoveAt(i);
   ImportService.Import(task); // do you need to pass the task or the task.Result
}

Seems to me however that there should be a better option. You could let the tasks and the import run and add a lock on the ImportService part for instance:

// This is the task code doing whatever
....
// Task finishes and calls ImportService.Import
lock(typeof(ImportService)) // actually the lock should probably be inside the Import method
{
   ImportService.Import(....);
}

There are several things bothering me with your requirements (including using a static ImportService, static classes are rarely a good idea), but without further details I can't provide better advice.

While this is likely not the most graceful solution, I would try launching the work tasks and have them place their output in a ConcurrentQueue. You could check the queue for work on a timer until all tasks are completed.

var rand = new Random();
var importedData = new List<string>();
var results = new ConcurrentQueue<string>();
var tasks = new List<Task<string>>
{
    new Task<string>(() =>
    {
        Thread.Sleep(rand.Next(1000, 5000));
        Debug.WriteLine("Task 1 Completed");
        return "ABC";
    }),
    new Task<string>(() =>
    {
        Thread.Sleep(rand.Next(1000, 5000));
        Debug.WriteLine("Task 2 Completed");
        return "FOO";
    }),
    new Task<string>(() =>
    {
        Thread.Sleep(rand.Next(1000, 5000));
        Debug.WriteLine("Task 3 Completed");
        return "BAR";
    })
};

tasks.ForEach(t =>
{
    t.ContinueWith(r => results.Enqueue(r.Result));
    t.Start();
});

var allTasksCompleted = new AutoResetEvent(false);
new Timer(state =>
{
    var timer = (Timer) state;
    string item;

    if (!results.TryDequeue(out item)) 
        return;

    importedData.Add(item);
    Debug.WriteLine("Imported " + item);

    if (importedData.Count == tasks.Count)
    {
        timer.Dispose();
        Debug.WriteLine("Completed.");
        allTasksCompleted.Set();
    }
}).Change(1000, 100);


allTasksCompleted.WaitOne();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM