views:

965

answers:

6

In a Windows Form window, multiple events can trigger an asynchronous method. This method downloads a file and caches it. My problem is that I want that method to be executed once. In other words, I want to prevent the file to be downloaded multiple times.

If the method downloading the file is triggered twice, I want the second call to wait for the file (or wait for the first method to be done).

Does someone have an idea on how to achieve that?

UPDATE: I am simply trying to prevent unnecessary downloads. In my case, when a client put its mouse over an item in a ListBox for more than a couple milliseconds, we start to download. We make the assumption that the user will click and request the file. What can potentially happen is that the user keeps his mouse over the item for one second and then click. In this case two downloads start. I am looking for the best way to handle such scenario.

UPDATE 2:: There is a possibility that the user will move its mouse over multiple items. In consequences, multiple downloads will occur. I've not really tough of this scenario, but right now if we face such scenario we don't abandon the download. The file will be downloaded (files are usually around 50-100kb) and then are going to be cached.

A: 

You can simply wrap your method call within a lock statement like this

private static readonly Object padLock = new Object();

...
lock(padLock)
{
    YourMethod();
}
Stefano Driussi
The Question states the method is asychronous, IOW it returns quickly having dispatch a download request. Hence this answer achieves nothing.
AnthonyWJones
That may be true, but he also states that he wants the second request to wait for the first to complete...thereby negating any async possibilities.
Matthew Brubaker
Actually, a second async call that realizes that the first is still processing would remain async from the POV of the caller. This would require all the checking to occur inside the asynchronous method itself.
Harper Shelby
Maybe i've been too fast on giving an answer and i assumed some things i shouldn't ...I'll wait for some more details (hoping not to loose other votes in the mean time :) )
Stefano Driussi
A: 

i'm not sure how it would be done in C#, but in java, you would synchonize on an private static final object in the class before downloading the file. This would block any further requests until the current one was completed. You could then check to see if the file was downloaded or not and act appropriately.

private static final Object lock = new Object();
private File theFile;

public method() {
  synchronized(lock) {
    if(theFile != null) {
      //download the file
    }
  }
}
Matthew Brubaker
synchonize {} in Java is roughly equivalent to lock {} in C#
Spencer Ruport
I was pretty sure there was an equivalent, I just didn't know what it was.
Matthew Brubaker
+6  A: 

Maintain the state of what's happening in a form variable and have your async method check that state before it does anything. Make sure you synchronize access to it, though! Mutexes and semaphores are good for this kind of thing.

If you can download different files simultaneously, you'll need to keep track of what's being downloaded in a list for reference.

If only one file can be downloaded at a time, and you don't want to queue things up, you could just unhook the event while something is being downloaded, too, and rehook it when the download is complete.

Michael Haren
Generally speaking you should use the lock{} statement whenever possible in .Net. Semaphores and Mutexes are a bit too robust for this kind of situation.
Spencer Ruport
I'd agree if the problem was more clearly defined. I did a lot of guessing.
Michael Haren
Any idea why this was down-voted? It seems like a perfectly reasonable answer to me.
Matthew Brubaker
Its the best answer so far.
AnthonyWJones
So if I understand well, you are telling me to do something similar to this: if (this.IsUpdating == false) { lock(padLock) { if (this.IsUpdating == false) { ... } } }. Does the doublechecking pattern would work here?
Martin
You should put the entire check in the lock. Though to be sure, you need to clarify what you want.
Michael Haren
Thanks Michael for your help. Please see my update.
Martin
I added some comments to your question--I need just a bit more
Michael Haren
I just added new details. Thanks again!
Martin
+1  A: 

Here is a dummy implementation that supports multiple file downloads:

    Dictionary<string, object> downloadLocks = new Dictionary<string, object>();

    void DownloadFile(string localFile, string url)
    {
        object fileLock; 
        lock (downloadLocks)
        {
            if (!downloadLocks.TryGetValue(url, out fileLock))
            {
                fileLock = new object(); 
                downloadLocks[url] = fileLock;
            }
        }

        lock (fileLock)
        {
            // check if file is already downloaded 

            // if not then download file
        }
    }
Sam Saffron
A: 

In general, I agree with Michael, use a lock around the code that actually gets the file. However, if there's a single event that always occurs first and you can always load the file then, consider using Futures. In the initial event, start the future running

Future<String> file = InThe.Future<String>(delegate { return LoadFile(); });

and in every other event, wait on the future's value

DoSomethingWith(file.Value);
Anthony Mastrean
A: 

If you want one thread to wait for another thread to finish a task, you probably want to use a ManualResetEvent. Maybe something like this:

private ManualResetEvent downloadCompleted = new ManualResetEvent();
private bool downloadStarted = false;

public void Download()
{
   bool doTheDownload = false;

   lock(downloadCompleted)
   {
       if (!downloadStarted)
       { 
            downloadCompleted.Reset();
            downloadStarted = true;
            doTheDownload = true;
       }
   }

   if (doTheDownload)
   {
       // Code to do the download

       lock(downloadCompleted)
       {
           downloadStarted = false;
       }
       // When finished notify anyone waiting.
       downloadCompleted.Set();
   }
   else
   {
       // Wait until it is done... 
       downloadCompleted.WaitOne();
   }

}
bobwienholt
note this will only support one download at a time, if you wanted 2 concurrent downloads of different files and only 2 threads, perhaps threads and queues with synchronisation are a better choice
Sam Saffron
I agree... but I was really just trying to illustrate the usage of ResetEvents and not implement the entire project for him. :)
bobwienholt