views:

57

answers:

3

Dear skilled. I’m developing an entity which allows user to copy multiple files in async manner with cancellation ability (and reporting progress as well). Obviously the process of copying runs in another thread, different from thread where CopyAsync was called.

My first implementation uses FileStream.BeginRead/BeginWrite with a buffer and reporting progress against number of usages of that buffer.

Later, for education purposes, I was trying to implement the same stuff thru Win32 CopyFileEx function. Eventually, I’ve stumbled upon the following thing: this function takes a pointer to bool value which is treated as cancellation indicator. According to MSDN this value is to be examined multiple times by Win32 during copying operation. When user sets this value to “false” the copying operation is cancelled.

The real problem for me is how to create a boolean value, pass it to Win32 and to make this value accessible for external user to give him an ability to cancel the copying operation. Obviously the user will call CancelAsync(object taskId), so my question is about how to get access to that boolean value in another thread fro my CancelAsync implementation.

My first attempt was to use Dictionary where key is an identifier of async operation and value points to allocated for boolean value memory slot. When user calls “CancelAsync(object taskId)” method, my class retrieves a pointer to that allocated memory from dictionary and writes “1” there.

Yesterday I’ve developed another solution which is based on creating a bool local variable in my method of copying and holding the address of that value in dictionary until copying operation completes. This approach could be described in the following lines of code (very simple and rough, just to illustrate an idea):

class Program
{
    // dictionary for storing operaitons identifiers
    public Dictionary<string, IntPtr> dict = new Dictionary<string,IntPtr>();

    static void Main(string[] args)
    {
        Program p = new Program();

        p.StartTheThread(); // start the copying operation, in my   
                            // implementation it will be a thread pool thread
    }

    ManualResetEvent mre;

    public void StartTheThread()
    {
        Thread t = new Thread(ThreadTask);
mre = new ManualResetEvent(false);
        t.Start(null);

        GC.Collect(); // just to ensure that such solution works :)
        GC.Collect();

        mre.WaitOne();

        unsafe // cancel the copying operation
        {
            IntPtr ptr = dict["one"];
            bool* boolPtr = (bool*)ptr; // obtaining a reference  
                                      // to local variable in another thread
            (*boolPtr) = false;
        }
    }

    public void ThreadTask(object state)
    { 
        // In this thread Win32 call to CopyFileEx will be
        bool var = true;
        unsafe
        {
            dict["one"] = (IntPtr)(&var); // fill a dictionary  
                                           // with cancellation identifier
        }
        mre.Set();

        // Actually Win32 CopyFileEx call will be here
        while(true)
        {
            Console.WriteLine("Dict:{0}", dict["one"]);
            Console.WriteLine("Var:{0}", var);
            Console.WriteLine("============");
            Thread.Sleep(1000);
        }
    }
}

Actually I’m a bit new to P/Invoke and all unsafe stuff so hesitating about latter approach for holding a reference to local value in dictionary and exposing this value to another thread.

Any other thoughts on how to expose that pointer to boolean in order to support cancellation of copying operation?

A: 

If your goal is to support being able to cancel a file copy operation in progress, I recommend using a CopyProgressRoutine. This gets called regularly during the copy, and allows you to cancel the operation with a return code. It will let you cancel the operation asynchronously without having to deal with pointers directly.

private class FileCopy
{
    private bool cancel = false;

    public void Copy(string existingFile, string newFile)
    {
        if (!CopyFileEx(existingFile, newFile, 
            CancelableCopyProgressRoutine, IntPtr.Zero, IntPtr.Zero, 0))
        {
            throw new Win32Exception();
        }
    }

    public void Abort()
    {
        cancel = true;
    }

    private CopyProgressResult CancelableCopyProgressRoutine(
        long TotalFileSize,
        long TotalBytesTransferred,
        long StreamSize,
        long StreamBytesTransferred,
        uint dwStreamNumber,
        CopyProgressCallbackReason dwCallbackReason,
        IntPtr hSourceFile,
        IntPtr hDestinationFile,
        IntPtr lpData)
    {
        return cancel ? CopyProgressResult.PROGRESS_CANCEL : 
            CopyProgressResult.PROGRESS_CONTINUE;
    }

    // Include p/inovke definitions from 
    // http://www.pinvoke.net/default.aspx/kernel32.copyfileex here
}

If you do want to use the pbCancel argument, then manually allocating unmanaged memory as you are already doing is probably the safest way to do it. Taking the address of a local variable is a little dangerous because the pointer will no longer be valid once the variable goes out of scope.

You could also use a boolean field in an object rather than a boolean local variable, but you will need to pin it in memory to prevent the garbage collector from moving it. You can do this either using the fixed statement or using GCHandle.Alloc.

Quartermeister
Thank you for the answer. According to API of my async entity (MS async event-based API) I could only rely on interaction with pbCancel. Eventually my inclination is using Marshal.AllocHGlobal.
xenn_33
+1  A: 

Ah, so that's what that other thread was about. There's a much better way to accomplish this, CopyFileEx() also supports a progress callback. That callback allows you to update the UI to show progress. And it allows you to cancel the copy, just return PROGRESS_CANCEL from the callback.

Visit pinvoke.net for the callback delegate declaration you'll need.

Hans Passant
Yep, I know about that ability. But actually the question is about architectural approach. According to MS event-based pattern my async entity must provide "XXXCancelAsync". In that method I should somehow notify pending operation that user has triggered a cancellation. I've already leveraged mentioned progress callback for generating "XXXProgressChanged" event (in appropriate synchronization context, of course).Maybe it does make sense to rely ONLY on that callback. So if user triggered cancellation this callback returns PROGRESS_CANCEL. In other case it will fire an event.
xenn_33
Yes, that makes sense to do it that way. An async cancel doesn't have to be instant. It will be instant from the client code perspective, you just don't generate ProgressChanged events any more.
Hans Passant
Eventually I've developed my async entity in such way. There is no necessity to allocate memory slots or use "fixed". Works great
xenn_33
A: 

Perhaps I'm missing something, but why couldn't you just use the defn already @ http://pinvoke.net/default.aspx/kernel32/CopyFileEx.html and then set the ref int (pbCancel) to 1 at cancel time?

James Manning
Actually I'm not 100% confident that GC wouldn't move that variable which was passed as ref parameter during execution of CopyFileEx.
xenn_33
sure, like any pointer being passed to native code you have to make sure the pointer doesn't move during the call, but a 'normal' local var int should be allocated on the stack and won't move during the call. If it's on the heap (if it's a member var in a class, for instance), then sure, you'll need to pin it.
James Manning