0

As a consequence of the problem described here:

WPD MTP stream hangs on commit

the UI of my WPF program is hanging (meaning it goes unresponsive while a certain task is performed), despite making the call using a BackgroundWorker. I have read something about this having something to do with the fact that the COM object on which the operations are performed is created on the UI thread, but when I break on the piece of code that hangs I see that it in fact is executed in a WorkerThread and not on MainThread, which seems to suggest that is not the problem (although I am a threading rookie).

Can anyone explain why this happens and how I should solve this UI hang?

Relevant code:

From base class, where the backgroundworker is set up:

public void Sync()
{
    SyncBackgroundWorker.WorkerReportsProgress = true;
    SyncBackgroundWorker.WorkerSupportsCancellation = true;
    SyncBackgroundWorker.DoWork += SyncBackgroundWorker_DoWork;
    SyncBackgroundWorker.ProgressChanged += SyncBackgroundWorker_ProgressChanged;
    SyncBackgroundWorker.RunWorkerCompleted += SyncBackgroundWorker_RunWorkerCompleted;

    SyncBackgroundWorker.RunWorkerAsync();
}

protected void SyncBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Retrieve needed files from iTunes and compose m3u playlists
    var syncInfo = this.getSyncInfo();

    // Save m3u files to music root on target
    this.saveM3UPlaylists(syncInfo.M3Us);
}

From class that extends base class (the last line initiates the file copy):

protected override void saveM3UPlaylists(List<FileInfo> M3UPlaylists)
{
    foreach (var file in this.Folder.GetFiles())
    {
        if (file.Extension == ".m3u")
            file.Delete();
    }

    int numPlaylists = this.Playlists.Count;
    for (int i = 0; i < numPlaylists; i++)
        PortableDevices.PortableDeviceFile.CopyFileToDevice(this.Folder, makeValidFileName(this.Playlists[i].Name + ".m3u"), M3UPlaylists[i]);
}

From relevant portable device class. The targetStream.Commit(0) part causes the actual freeze, as described in the previously given link. Note that the the COM-object (IPortableDevice) parent.Device.Content has been previously initialized, when the connection with the device was set-up, at the start of the program.

public static void CopyFileToDevice(PortableDeviceFolder parent, string name, FileInfo file)
{
    IPortableDeviceValues values = GetRequiredPropertiesForContentType(parent.Id, name, file.Length);

    PortableDeviceApiLib.IStream tempStream;
    uint blockSize = 0;
    parent.Device.Content.CreateObjectWithPropertiesAndData(
        values,
        out tempStream,
        ref blockSize,
        null);

    System.Runtime.InteropServices.ComTypes.IStream targetStream =
        (System.Runtime.InteropServices.ComTypes.IStream)tempStream;
    try
    {
        using (var sourceStream = file.OpenRead())
        {
            var buffer = new byte[blockSize];
            int bytesRead;
            do
            {
                bytesRead = sourceStream.Read(buffer, 0, (int)blockSize);
                targetStream.Write(buffer, bytesRead, IntPtr.Zero);
            } while (bytesRead > 0);
        }

        targetStream.Commit(0);
    }
    finally
    {
        Marshal.ReleaseComObject(tempStream);
    }
    parent.Refresh();
}
Community
  • 1
  • 1
NinjaTuna
  • 41
  • 5
  • 2
    How could we possibly do this without seeing your code? Can you please post your code? – rory.ap Jan 25 '15 at 15:05
  • check this post http://developmentpassion.blogspot.com/2014/10/application-ui-hangs-on-long-running.html – Ehsan Sajjad Jan 25 '15 at 15:07
  • @EhsanSajjad, I believe that is what I have done already, see the code I added – NinjaTuna Jan 25 '15 at 16:41
  • @roryap, I added the relevant code. I did not do so at first because I thought it might have overcomplicated things. Hope it helps. – NinjaTuna Jan 25 '15 at 16:41
  • Well, it is exactly the problem that you described. Keeping objects thread-safe is something that happens automatically in COM without you having to help. It does the exact equivalent of Dispatcher.Invoke() because you created the object on the UI thread. That makes your UI freeze. Either create the object on your worker thread or, if you need to keep it alive beyond the worker's completion, create a [dedicated thread](http://stackoverflow.com/a/21684059/17034) to give the object a safe home. – Hans Passant Jan 25 '15 at 16:58
  • Please be specific about what you mean by "freeze". Normally, that word is used to describe a complete deadlock; i.e. the program has no hope of making forward progress. Even if the COM object is owned by your UI thread, and consequently performs its operations there, that would not in and of itself cause deadlock. That could happen only if your UI thread is already blocked, waiting on the COM object, which is a fixable situation. If you simply mean that the UI thread periodically pauses, due to the longer-running op. executed there, then yes, you need to give the COM object its own STA thread – Peter Duniho Jan 25 '15 at 19:31
  • @PeterDuniho, excuse my terminology, maybe 'hang' would be a better description. A certain operation performed on/by the COM object takes a long time (but _does_ actually finish), which makes the UI unresponsive until it is finished. I think I will have to look into this STA thread thing. – NinjaTuna Jan 26 '15 at 10:13
  • @HansPassant, Okay, thanks a bunch! Deriving the class where my COM device object resides in from the code you linked and initializing the COM-object itself overriding the Initialize() function works like a charm. I guess this is solved then, although the actual reason the operation (IStream.Commit(0)) takes such a long time remains a mystery to me. – NinjaTuna Jan 26 '15 at 11:03

0 Answers0