0

I am trying to perform multiple database calls using async and await features available in .NET Framework 4.5. This is the first time I am implementing this feature.

if each query is taking 7 seconds, it used to take 35 seconds(5 queries * 7 seconds). With below implementation, I was expecting it should fetch and populate controls in asp page in close to 7-9 seconds. However, it is still taking 35 seconds, proving me the synchronous behavior.

can someone please help me where I am going wrong with this below asynchronous implementation.

I appreciate on any inputs, I have been breaking my head around this since couple of days

    protected void Page_Load(object sender, System.EventArgs e)
    {
        RegisterAsyncTask(new PageAsyncTask(FillControlsAsync));
    }

    public async Task FillControlsAsync()
    {
         Task[] tasks = new Task[]{
         PopulateControlTask(query1, "controlID1"),
         PopulateControlTask(query2, "controlID2"),
         PopulateControlTask(query3, "controlID3"),
         PopulateControlTask(query4, "controlID4"),
         PopulateControlTask(query5, "controlID5")
        });

        await Task.WhenAll(tasks);
    }
    public async Task PopulateControlTask(string query, string control)
    {
       await Task.Run(() =>
           {
               DataSet ds;
               OracleCommand cmd;
               OracleDataAdapter da;
               try
               {
                   if (!Page.IsPostBack)
                   {
                       cmd = new OracleCommand(query, cn);
                       da = new OracleDataAdapter(cmd);
                       ds = new DataSet();
                       da.Fill(ds);
                       switch (control)
                       {
                           case "controlID1":
                                //some custom code for control 1
                                // like attaching the datasource to control.
                               break;
                           case "controlID2":
                               //some custom code for control 2
                               break;
                            case "controlID2":
                            //some custom code for control 3
                            break;
                            case "controlID3":
                            //some custom code for control 4
                            break;
                            case "controlID4":
                            //some custom code for control 5
                            break;
                    }
                }
            }
            catch(Exception e)
            {
                 //some error handling here
            }
        });
    }
mason
  • 31,774
  • 10
  • 77
  • 121
knowise
  • 15
  • 2
  • 5
  • Do you really have an empty catch block? That's an anti-pattern! Don't do that. – mason Apr 04 '16 at 23:48
  • I'm wondering whether you want to retrieve data, or just execute query. If you need to retrieve data, above code won't work, because it just executes a task in a separate thread which doesn't return anything. – Win Apr 04 '16 at 23:51
  • Possible duplicate of [Async I/O intensive code is running slower than non-async, why?](http://stackoverflow.com/questions/28544557/async-i-o-intensive-code-is-running-slower-than-non-async-why) – mason Apr 04 '16 at 23:57
  • @mason sorry, I missed adding my complete code above. I am handling the exceptions, I don't have an empty catch block in my actual code. Thank you for responding – knowise Apr 05 '16 at 00:00
  • @Win after executing the query, I am filling the data set and if you see in my case statements, I have added a comment saying attaching datasource to control. I did not enter that part but, I am taking the data set and using that to attach the datasource of a listbox control in the case statement. – knowise Apr 05 '16 at 00:03
  • You know when you await something, you're waiting for it to finish right? So in effect this is all executing sequentially. By the time PopulateControlTask is done, the data is already bound to the control. If you wanted to use Task.WhenAll, you wouldn't await the task inside PopulateControlTask. Also, none of this matters much because as I answered in the link I provided earlier, the Oracle library doesn't support async/await properly. – mason Apr 05 '16 at 00:12
  • @mason Oracle doesn't support? I didn't knew that before. any other approach that you can suggest for me to achieve this asynchronous behavior in web forms? Other than using jquery Ajax? – knowise Apr 05 '16 at 00:14
  • 1
    Not with Oracle. I pressured an Oracle dev that supported the Oracle .NET Managed Client a year or so ago that they needed to get async support working properly. He said they didn't really have it in their plans. – mason Apr 05 '16 at 00:18
  • @mason that's sad to hear. Problem is I have like 20 controls in the web page. And currently the code is synchronously calling the database and each query is taking upto 7 seconds to return the result. Hence it's taking 140-150 seconds to load the entire web page. – knowise Apr 05 '16 at 00:40
  • That seems too long anyways. How long do those same queries take to run directly on the DB server? How many rows of results does each query return? – mason Apr 05 '16 at 00:42
  • @mason each query takes upto 7 seconds to return the result in DB server as well. My expectations with async is the ability to run these 20 queries asynchronously and load the page is just 7-10 seconds altogether. – knowise Apr 05 '16 at 00:54
  • Yes, but unfortunately the Oracle driver doesn't work like that. But anyways, a 7 second query seems like an awful long time in and of itself. Again, how many rows are these queries returning? Are they complicated queries? Have you had a DBA examine the explain plans? – mason Apr 05 '16 at 00:55
  • @mason most of them return 30-50 rows, retrieving the distinct values from the tables( with millions of records in it ), I see that I can improve the DB design to run the queries a bit faster, but for now I trying hard to get this done using async – knowise Apr 05 '16 at 00:56
  • @mason yes, some of them are a bit complex queries and I have had DBA to examine the queries. It used to take much more time before. With his changes, it came down to 4-6 seconds. Before I go bother him again, I was trying to look for alternate ways to achieve the need in some kind of asynchronous fashion. – knowise Apr 05 '16 at 01:01
  • Does the data change often? Perhaps you can cache the results or store them in a materialized view. – mason Apr 05 '16 at 01:12
  • @mason data does change very often. And users are always interested in data which is not old more than 30 mins - an hour – knowise Apr 05 '16 at 01:16
  • Perhaps you could have a background process run every few minutes and do the queries. Cache the results so they're ready for immediate viewing. – mason Apr 05 '16 at 01:17
  • @mason sounds like a good idea. Thank you. So before I switch my gears, is it not worthy spending my time in trying to figure a way out to implement any kind of asynchronous behavior? – knowise Apr 05 '16 at 01:21
  • You have an Oracle support contract? I'd utilize that to ask Oracle directly. Anyways, if you want to do background tasks in ASP.NET, [read this](http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx). – mason Apr 05 '16 at 01:25
  • @mason I'll have to check in regards to the Oracle support. I'll do that. Thanks for the link. Will go through it – knowise Apr 05 '16 at 01:28

3 Answers3

5

async and await are for asynchronous code. Normally, if you had a scalable database, you could make your db calls asynchronous and thus scale your server. Note that the primary benefit of async on ASP.NET is scalability, not response time.

However, as others have noted, Oracle doesn't support asynchronous code.

But that is immaterial, since the code you posted isn't actually asynchronous to begin with! It's what I call "fake asynchronous", because it's just pushing synchronous work off to a background thread using Task.Run instead of using naturally-asynchronous APIs. (But as already noted, in this case (that is, Oracle), you don't have any naturally-asynchronous APIs to work with).

So, what you end up with is parallelism, not asynchrony. In particular, the code is spreading itself over 5 thread pool threads to do its work.

Now, the first thing you need to do is ask yourself if you really want parallelism on the server. Your requests will take 5 threads instead of 1 (or 0). This can greatly impact the scalability of your web server (in a bad way). Also, take into consideration the capabilities of your backend. If it's a single server and these queries are all hitting a single database on a single hard drive, will parallelizing 5 of them actually produce any benefit or will it actually be just as bad if not worse due to disk contention? (You should be able to whip up a quick console app to test how your db responds with serial vs parallel requests, when idle and when under load).

I find that the vast majority of the time, the answer is "no, I do not want to bring my entire db server to its knees for this one request" - in other words, avoid parallelism on the server.

But if you have weighed the options and decided that yes, yours is one of the rare cases where parallelism is appropriate on ASP.NET, then you should ask the question that you've posted here: why are these running sequentially and not concurrently? (side note: it's sequential vs concurrent here, not synchronous vs asynchronous)

Answer: I don't know.

But I have a guess: if the database connection (cn in your code snippet) is shared, then it's likely that the db connection itself is limited to one query at a time. Other database connection systems have similar restrictions. The first thing I'd try is giving each query its own connection.

That is, if you want to parallelize your web server. Which is a big "if".

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Paralleling web server is definitely a "no". Thank you for putting everything together. so, now it's my understanding that even if I were connecting to **SQL Server** and implement the above code, it would still **NOT** be a asynchronous fashion of bringing in the results from the DB unless I use asynchronous apis ? – knowise Apr 05 '16 at 15:18
  • @CaptainRG: That is correct; you would need to use asynchronous calls instead of `Task.Run`. I have done this (with Azure SQL, not plain SQL Server) using EF6 asynchronous queries, and you can get them running concurrently if each one has its own db connection. That is, they use asynchronous (0-threaded) concurrency, not parallel (multi-threaded) concurrency. – Stephen Cleary Apr 05 '16 at 15:39
  • can you also please throw some light on how is the above parallel (multi-threaded ) concurrency is different from multi-threading? – knowise Apr 12 '16 at 00:43
  • @CaptainRG: Parallelism uses multiple threads; asynchrony [uses no threads](http://blog.stephencleary.com/2013/11/there-is-no-thread.html). – Stephen Cleary Apr 12 '16 at 02:44
  • Sorry, Ill ask my question other way around. Earlier I was implementing asynchronous feature as above, and I have learnt that doing in that way, ill end up paralleling the webserver. Which means if I create 5 tasks for loading 5 database queries, ill end up creating 5 threads which is what we called multi threaded concurrency (as far as my above example is concerned). Having said that, is multi threading concept in c# (system.threading library) and parallelism with async is essentially similar behind the scenes? Sorry for asking the same question again. – knowise Apr 12 '16 at 03:00
  • @CaptainRG: No, asynchrony is completely different than parallelism. Your original code is concurrency via parallelism - 5 threads. Using Task.WhenAll with EF6 async queries (with an async db driver) would be concurrency via asynchrony - 0 threads. – Stephen Cleary Apr 12 '16 at 09:01
2

To follow up on other answers about Oracle's async implementation (don't have enough reputation to comment), Oracle's async methods are not truly async and just call synchronous methods under the covers, so calling them is worse for performance than the synchronous methods.

You can watch https://github.com/oracle/dotnet-db-samples/issues/144 to see if they will provide a true async implementation. As of Q1 2022, the Oracle .NET team is saying maybe Q4 2022.

iinuwa
  • 444
  • 4
  • 14
1

ODP.NET Core and Managed ODP.NET 23.2 Dev Release (preview version of packages) now fully support async await: Announcing ODP.NET 23c Dev Release

Giorgi
  • 30,270
  • 13
  • 89
  • 125