16

I am trying to run 3 database queries in parallel but I'm not sure that I am doing it correctly.

I have made 3 functions which each make a query to the database.

private static async Task<string> getAccountCodeAsync(string deviceId)
{
    long deviceIdLong = long.Parse(deviceId);
    using (var db = new NetworksEntities())
    {
        return db.Devices.Where(x => x.DeviceId == deviceIdLong)
            .Select(x => x.AccountCode)
            .FirstOrDefault();
    }
}

private static async Task<string> getDeviceTypeAsync(string deviceId)
{
    long deviceIdLong = long.Parse(deviceId);
    using (var db = new NetworksEntities())
    {
        return db.Devices.Where(x => x.DeviceId == deviceIdLong)
            .Select(x => x.DeviceType)
            .FirstOrDefault()
            .DeviceType;
    }
}

private static async Task<string> getUserNameAsync(string userId)
{
    int userIdInt;
    Int32.TryParse(userId, out userIdInt);
    using (var db = new NetworksEntities())
    {
        return db.Users.Where(x => x.UserId == userIdInt)
            .Select(x => x.Email)
            .FirstOrDefault();
    }
}   

I then in my code I run the three tasks:

var TaskAccountCode = await getAccountCodeAsync(deviceId);
var TaskDeviceType = await getDeviceTypeAsync(deviceId);
var TaskUsername = await getUserNameAsync(userId);
await Task.WhenAll();   
// use the results from my 3 tasks to make a new insert into the db.

Is what I am doing actually running my 3 db queries in parallel?

EDIT:

private static async Task<string> getAccountCodeAsync(string deviceId)
{
    long deviceIdLong = long.Parse(deviceId);
    using (var db = new NetworksEntities())
    {               
        return db.Devices.Where(x => x.DeviceId == deviceIdLong)
            .Select(x => x.AccountCode)
            .FirstOrDefaultAsync();
    }
}
CarenRose
  • 1,266
  • 1
  • 12
  • 24
Zapnologica
  • 22,170
  • 44
  • 158
  • 253
  • 1
    No they run synchronously if you call them like that. using await it will run first method, then the second one...etc. To run them in parallel do it like this: Task.WhenAll(TaskAccountCode, TaskDeviceType,TaskUsername) and remove await. – Mihai Hantea Apr 08 '14 at 06:20
  • 1
    Refer to http://stackoverflow.com/questions/19102966/parallel-foreach-vs-task-run-and-task-whenall – Mukus Apr 08 '14 at 06:26

2 Answers2

17

You will have to change the last part of the code to make it run in parallel:

var taskAccountCode = getAccountCodeAsync(deviceId);
var taskDeviceType = getDeviceTypeAsync(deviceId);
var taskUsername = getUserNameAsync(userId);
await Task.WhenAll(taskAccountCode, taskDeviceType, taskUsername);
var accountCode = taskAccountCode.Result;
var deviceType = taskDeviceType.Result;
var username  = taskUsername.Result;

Notice that there is only one await. In your original code you await every task one after the other making them run in sequence instead of in parallel.

Also, the methods getAccountCodeAsync etc. are not really async methods (you should get a compiler warning about this). However, Entity Framework 6 has support for async operations and to use that you should replace FirstOrDefault with FirstOrDefaultAsync. For each parallel operation you will have to use a separate context, and that is exactly what you are doing.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • Do I have to specify the tasks names in the `Task.WhenAll();` ? – Zapnologica Apr 08 '14 at 06:35
  • Youa re correct, I do get compiler warnings on the async methods, but there is no async for reading, I know there is a async for savechanges. And will this run each task on its own thread? – Zapnologica Apr 08 '14 at 06:37
  • 1
    @Zapnologica: _Do I have to specify the tasks names in the Task.WhenAll(); ?_ Yes you have to. – Martin Liversage Apr 08 '14 at 06:42
  • I tried changing to firstOrDefaultAsync() but my compiler didn't like it. – Zapnologica Apr 08 '14 at 06:46
  • @Zapnologica: Aside from the casing error which I assume isn't the issue you need to use Entity Framework 6. Otherwise you will have to start your own tasks using something like `Task.Factory.StartNew`. – Martin Liversage Apr 08 '14 at 06:58
  • Currently using EF 6.1.0 and there is no FirstOrDefaultAsync() , the only thing I can find is the method `FindAsync()` Am I doing something wrong here? – Zapnologica Apr 08 '14 at 08:10
  • @Zapnologica: Provided that type type of `NetworkEntities.Devices` is `DbSet` (where `T` is replaced by your actual device type) and that you have `using System.Data.Entity` at the top of your C# file you should have access to the `FirstOrDefaultAsync` extension method (I personally verified this in a small project using EF 6.1). The important point is that `Select` returns `IQueryable` and not `IEnumerable` and that the namespace is imported allowing you to use the extension method. – Martin Liversage Apr 08 '14 at 09:01
  • aah i added the using and it seemed to have worked now, But now my asyn methods want me to return a string and not a task? Should I `return await db.table.forstordefaultasync()`; – Zapnologica Apr 08 '14 at 09:11
  • @Zapnologica: `FirstOrDefaultAsync()` returns a task and you want to return that task from your method marked with `async`. You should not await that task except as I described in my answer using `Task.WhenAll`. – Martin Liversage Apr 08 '14 at 09:37
  • in my case `db.FSKDevices.Where(x => x.FSKDeviceId == deviceIdLong).Select(x => x.AccountCode).FirstOrDefaultAsync();` wants to return a String. I tis onyl happy unless I await it in the method, or if I return a string? – Zapnologica Apr 08 '14 at 09:53
  • Sorry for all the questions, but its just not working like you are explaining. Please see my edit, that is the code I currently have. It tell me to return a string, if I do then the async method moans that it has to return a TASK<> – Zapnologica Apr 08 '14 at 09:58
  • @Zapnologica: Who _wants to return a string_ and who _moans_? I assume that you are getting compiler errors. `getAccountCodeAsync` return `Task` thus you want `FirstOrDefaultAsync()` to return `Task`. This means that `Select(...)` should return `IQueryable`. If `AccountCode` is a string property your code looks allright. If you get compiler errors you will have to fix them by reading the description. For good measure I repeat: do __not__ `await` in `getAccountCodeAsync` if you want to enable parallel execution. – Martin Liversage Apr 08 '14 at 10:22
2

No they run one after eachother if you call them like that using await foreach method, it will run first method, then the second one...etc. And the last part await Task.WhenAll(), you did not supplied the tasks to wait for completion.

To run them in parallel you have to do it like this:

var TaskAccountCode = getAccountCodeAsync(deviceId);
var TaskDeviceType = getDeviceTypeAsync(deviceId);
var TaskUsername = getUserNameAsync(userId);
await Task.WhenAll(TaskAccountCode, TaskDeviceType,TaskUsername);   
Mihai Hantea
  • 1,741
  • 13
  • 16