views:

1008

answers:

5

I am developing a C# Winforms application, part of the application will be uploading files to a webserver using AsyncUpload (using it,due to the need to use a porgress callback) , In the C# program

i got a simple for loop that calls The Uploading function

 for(int i=0;i < 10 ; i++)
{
  Uploadfun();
}

And the fun does some magic:

Uploadfun()
  { 
  // Logic comes here

   // webClient.UploadFileAsync runs a 2nd thread to perform upload .. 
   webClient.UploadFileAsync(uri, "PUT", fileNameOnHD);  

 }

And a callback that gets called when the Async upload is done

Upload_Completed_callback()
{
  //Callback event
}

Edit

The logic sequence:

  1. Fun gets called (from loop)
  2. Fun logic is executed and done..
  3. Goes back to for loop
  4. Callback will be called eventually, when UploadFileAsync (which is running some logic in another thread) will end

The problem is on the 3rd point, when the execution moves back to the for loop, i need to block the loop from continuing until the callback get called.

+4  A: 

In C# methods block by default, so you shouldn't need to do anything. I'm assuming that for some reason you are calling a non-blocking method that starts a background task / thread / whatever and gives you a callback when it's done. You want to call this asynchonous method in a synchronous manner.

You can call fun from inside the callback. Something along these lines (pseudo-code):

int n;

callFunTenTimes()
{
    n = 0;
    fun(n);
}

callback()
{
    ++n;
    if (n < 10)
       fun(n);
    else
       print("done");
}

This is similar to continuation passing style.

An advantage to this method is that you can also make your method asynchronous without adding any extra threads, locks, or extra logic - you just supply a callback function which your client can subscribe to. It works well in an event-driven environment.

Mark Byers
Thats funny. Ive been thinking about my own problem along these lines: having the callback function do the 'meat' of what the loop used to do. This solution seems to keep the wait time to a minimum.
Joe
i need the async version because it provides a "progress" callback which is a core requirement.
Madi D.
@Madi D: If the requirement is that the upload must behave asynchronously, why not make your function an async function too, with a callback? Can you explain more about what you are using this for? Is this a WinForms app, ASP.NET, etc?
Mark Byers
@Mark: i edited the question and added answers to you questions, plus some extra info.
Madi D.
+1 for teaching me a new concept, and giving me an alternative solution!
Madi D.
+10  A: 

So if I understand correctly, you want to call UploadFileAsync then block until the async call has hit your callback. If so, I'd use AutoResetEvent i.e

private readonly AutoResetEvent _signal = new AutoResetEvent(false); 

fun()
  { 
  // Logic comes here

   // runs a 2nd thread to perform upload .. calling "callback()" when done
   webClient.UploadFileAsync(uri, "PUT", fileNameOnHD);  

   _signal.WaitOne();   // wait for the async call to complete and hit the callback     
 }



callback()
 {
   //Callback event
   _signal.Set(); // signal that the async upload completed
 }

Using AutoResetEvent means that the state gets automatically reset after Set has been called and a waiting thread receives the signal via WaitOne

zebrabox
when using the Waitone() after the uploadAsync , The program is halting, and the Callback is not being called.
Madi D.
okie.. i fixed the halting problem,called fun() in a separate thread, "obviously" calling it in the main thread is causing the issue !, @Juliet Kinda helped point out the problem.. thanks both of you =)
Madi D.
+1  A: 

The problem is here:

for(int i=0;i < 10 ; i++)
{
  fun(); <-- if we block until this function finishes here, we stop the UI thread
}

What you're doing is sequential. And if you can't afford to block the UI thread, move the loop off the UI thread:

volatile downloadComplete;

void DownloadUpdates()
{
    ThreadPool.QueueUserWorkItem(state =>
        for(int i = 0; i < 10; i++)
        {
            downloadComplete = false;
            webClient.UploadFileAsync(uri, "PUT", fileNameOnHD);
            while(!downloadComplete) { Thread.Sleep(1); }
        });
}

Upload_Completed_callback()
{
    downloadComplete = true;
}

Now you can block the execution of the loop without halting your UI thread, and you also get the benefit of progress indicators from the webclient class.

Juliet
Don't use a boolean variable for this, the compiler can optimize it away (which would cause the behavior of never continuing that the OP mentions). zebrabox showed the right way to do it, with a waitable event object.
Ben Voigt
@Ben: I appreciate your comments, but I think they're misleading. First, the compiler isn't going to optimize the bool away as long as its used or assigned somewhere (and presumably it is). Second, spin-waiting on a bool and resetEvents are valid ways to block a thread, both are the "right way to do it", neither are "wrong", choosing one or the other depends on the tastes of the programmer.
Juliet
Thanks alot for the really useful answer,endded up combining the threading from your solution with @zebrabox's resetEvent solution, and now it is working.. =)
Madi D.
@Juliet,According to the rules of the .NET memory model, these two are exactly equivalent:while(!downloadComplete) { Thread.Sleep(1); }andif (!downloadComplete) { while (true) { Thread.Sleep(1); } }Since the loop doesn't change the variable, the compiler can move the test outside the loop.I see that now you've added a syntax error trying to make downloadComplete volatile. If you fix the declaration, it will prevent the compiler from doing the optimization I mentioned, but it is still polling which is worse than the event for a number of reasons.
Ben Voigt
@Ben: I appreciate your comment, but `while(!downloadComplete) { Thread.Sleep(1); }` and `if (!downloadComplete) { while (true) { Thread.Sleep(1); } }` are *not* equivalent in any memory model. Would you mind providing some MSDN documentation or a code sample demonstrating how polling compiles into the code you've provided (with or without volatile)?
Juliet
@Juliet: Here's a very informative discussion in the context of Java, but this particular case is identical in C# (because the optimization is legal under sequential consistency and both Java and .NET CLR memory models are weaker than that):http://java.sun.com/docs/books/jls/third_edition/html/memory.htmlLook especially at section 17.9 of that page.
Ben Voigt
+2  A: 

Zebrabox does have it right using a WaitHandle. While Juliet's solution does work, the thread performing the spin-wait will consume a significant amount of processor in proportion to the WaitHandle, which would essentially be sitting idle.

Grant Back
+1 shedding light on both the top answers (to me)..
Madi D.
It won't consume very much processor, but it may prevent the system from putting the CPU into a deeper idle state and hence draw down the battery faster on a laptop.
Ben Voigt
A: 

Hi Medi: How did you get your probelm solved? I have the same problem, but couln't make it work. In my async function, it also does some UI updates. I think that is the problem. If I remove the UI update, it works. From my reading, you also have UI updates in your async function, right?

miliu
Are you getting cross-thread access error? if so, that is a different problem, to solve it you need to use Invoke, check the following link http://stackoverflow.com/questions/661561/how-to-update-gui-from-another-thread-in-c
Madi D.
No, cross-thread access is not my problem. You said you solved your problem using a combination of event and bool flag. I'm wondering how the event is used in your case, because it's blocking the UI update if I use event. In the meantime, I found a solution using Juliet's suggestion, but i have to add Application.DoEvents() before Thread.Sleep(1) in the while (!downloadComplete) loop. I'd like to avoid DoEvents (because many people think it's a bad thing). The only problem now is that the UI update through the second thread is happening, but the app doesn't act on user mouse click.
miliu