C# WPF - Flytta videofiler som används av programmet

Permalänk
Entusiast

C# WPF - Flytta videofiler som används av programmet

Hej, jag behöver kunna flytta en videofil som precis har visats i min applikation, men den går inte att flytta eftersom den används av programmet.

System.IO.IOException: The process cannot access the file because it is being used by another process.

Videofilen ligger i ett MediaElement:

<MediaElement x:Name="mainVideo" Stretch="None" VerticalAlignment="Center" HorizontalAlignment="Center" MediaEnded="mainVideo_MediaEnded" LoadedBehavior="Manual" />

Och jag väljer source via koden bakom:

Uri uri = new Uri(filePaths[0]); mainVideo.Source = uri; mainVideo.Play();

Problemet är att jag inte lyckas får applikationen att släppa låset på filen eftersom den används.

Av googlande har jag kommit fram till att det är något sånt här jag ska göra:

mainVideo.Stop(); mainVideo.Close(); mainVideo.Source = null;

Men det hjälper inte. Felet återstår.

Några idéer?

Permalänk
Avstängd

Googla "MediaElement release file" är mitt tips

edit: För övrigt kan jag tipsa dig om att läsa på om MVVM, det du gör nu är ett antipattern

Visa signatur
Permalänk
Entusiast

@CyberVillain: Jag har redan spenderat de tre senaste dagarna med att googlat på det, börjar tappa hoppet..

Men om jag förstår MVVM rätt, så har jag följt den approachen för bildfiler:

Uri uri = new Uri(filePaths[0]); BitmapImage image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; image.UriSource = uri; image.EndInit(); mainImage.Source = image;

Eller?
Hade gärna gjort lika dant för video med men jag har ingen aning om hur det skulle gå till.

Permalänk
Datavetare

Sista posten i denna tråd verkar ha "löst" samma problem.

Min gissning här är att denna lösning mest bara gör att det fel du nu ser blir mindre sannolikt då man ökar tiden från där filen kan släppas till dess att den faktiskt släps. Det är dock inte helt omöjligt att detta faktiskt är något som slutgiltigen löser problemet, enda sättet att reda ut det helt är att reda ut exakt vad som anropas från att man sätter IsEnabled till false till dessa att följande sats körs. Under denna tid befinner man sig i komponenten så den har ju teoretiskt sett möjlighet att utföra "rätt sak" t.ex. efter att IsEnabledChanged returnerar och "upptäcker" att Source är null.

Om vi antar att sannolikheten att filen är låst signifikant minskar om man går omvägen via IsEnabledChanged eventet men att det inte är en korrekt lösning, vad kan det bero på? En hyfsat kvalificerad gissning är att man låter en dtor eller motsvarande (något internt kan ju även använda IDisposable, MediaElement verkar inte implementera detta). Så en väg runt detta problem att man nu inte kan räkna med att filen släps efter att Source sätts till null (att sätta Source till null verkar i alla fall vara ett krav för att detta ska ha någon chans att fungera).

Om mitt spekulerande är rätt så, utan att överhuvudtaget ha testat, borde detta vara en väg runt problemet som också undviker alla race-conditions:

mainVideo.Stop(); mainVideo.Clear(); mainVideo.Source = null; // Tvinga fram en GC av alla generationer System.GC.Collect(); // Nu finns ändå en del race, t.ex. kan GC redan köras på någon annan tråd // eller anropet ovan kanske lägger ut jobbet på en annan tråd. Måste då // se till att denna tråd väntar till GC kört klart System.GC.WaitForPendingFinalizers(); // om jag gissat rätt om problemet borde du kunna radera filen här

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Avstängd

Verkar inte var den bäst skrivna klassen om man måste GCa hela Appdomänen för att filen ska släppas.

Du kan även implantera lite halvhackigt, pseudokod

public void Delete() { try { File.Delete(myFile); } catch(IOExecption e) //Jag bara antar att det blir ett IO ex här { Application.Current.Dispatcher.Invoke(Delete); } }

Visa signatur
Permalänk
Avstängd

Angående MVVM här är en lösning som är rätt snygg, kräver dock MVVM-ramverket Caliburn.Micro, som jag iof tycker är ett krav på en WPF-app idag

http://stackoverflow.com/questions/7446850/playing-a-sound-in...

Visa signatur
Permalänk
Entusiast
Skrivet av Yoshman:

Sista posten i denna tråd verkar ha "löst" samma problem.

Min gissning här är att denna lösning mest bara gör att det fel du nu ser blir mindre sannolikt då man ökar tiden från där filen kan släppas till dess att den faktiskt släps. Det är dock inte helt omöjligt att detta faktiskt är något som slutgiltigen löser problemet, enda sättet att reda ut det helt är att reda ut exakt vad som anropas från att man sätter IsEnabled till false till dessa att följande sats körs. Under denna tid befinner man sig i komponenten så den har ju teoretiskt sett möjlighet att utföra "rätt sak" t.ex. efter att IsEnabledChanged returnerar och "upptäcker" att Source är null.

Om vi antar att sannolikheten att filen är låst signifikant minskar om man går omvägen via IsEnabledChanged eventet men att det inte är en korrekt lösning, vad kan det bero på? En hyfsat kvalificerad gissning är att man låter en dtor eller motsvarande (något internt kan ju även använda IDisposable, MediaElement verkar inte implementera detta). Så en väg runt detta problem att man nu inte kan räkna med att filen släps efter att Source sätts till null (att sätta Source till null verkar i alla fall vara ett krav för att detta ska ha någon chans att fungera).

Om mitt spekulerande är rätt så, utan att överhuvudtaget ha testat, borde detta vara en väg runt problemet som också undviker alla race-conditions:

mainVideo.Stop(); mainVideo.Clear(); mainVideo.Source = null; // Tvinga fram en GC av alla generationer System.GC.Collect(); // Nu finns ändå en del race, t.ex. kan GC redan köras på någon annan tråd // eller anropet ovan kanske lägger ut jobbet på en annan tråd. Måste då // se till att denna tråd väntar till GC kört klart System.GC.WaitForPendingFinalizers(); // om jag gissat rätt om problemet borde du kunna radera filen här

Tyvärr rättade inte din kod till felet. Jag försökte dock efterlikna "lösningen" i tråden du länkade genom att flytta själva flytten av filen. Nu sker flytten efter att lite annat har hänt. Detta löser problemet om man tar det lugnt. Men flyttar man större filer för nära inpå varandra så hinner den inte med.

Det känns som att jag kan behöva en annan approach till problemet. Detta är dock det första jag programmerar i C#, har läst Java innan. Så jag vet inte riktigt vad som finns att tillgå.

Jag tänker mig att man kanske kan skapa någon typ av kö som kör filöverföringen i en egen tråd, som man dessutom kan stoppa nya filer i under tiden den jobbar.

Jag testade nyss med Task.Factory.StartNew(() => moveFile()); med en Thread.sleep(1000) i för att ge garbage collectorn tid, vilket slutade i fullständigt fiasko. Hälften av filerna jag flyttade bytte namn med varandra...

Några idéer om hur det görs bäst? Om det ens är en bra idé d.v.s.

Skrivet av CyberVillain:

Verkar inte var den bäst skrivna klassen om man måste GCa hela Appdomänen för att filen ska släppas.

Du kan även implantera lite halvhackigt, pseudokod

public void Delete() { try { File.Delete(myFile); } catch(IOExecption e) //Jag bara antar att det blir ett IO ex här { Application.Current.Dispatcher.Invoke(Delete); } }

Den där koden är ganska lik min move-metod:

private void moveFile(string from, string to) { try { File.Move(from, to); // Try to move Properties.Settings.Default.ImagesMoved += 1; Properties.Settings.Default.Save(); } catch (IOException ex) { Debug.WriteLine(ex); // Print error } }

Men vad gör denna raden?
Application.Current.Dispatcher.Invoke(Delete);
Jag vill ju inte ta bort filen från disken, bara från minnet. Filen ska flyttas.

Permalänk
Avstängd

Den ger UI tråden lite tid att uppdatera och sedan försöker den tabort filen igen. Du kan byta ut delete till Move

Visa signatur
Permalänk
Entusiast

@CyberVillain: Fick inte det att fungera med move, vet inte om det finns ens.


Men jag har löst* problemet!
*Löst som i att lyckas flytta filerna, inte att få dem fria i tid.

Jag vet inte om detta hjälper någon men jag kan ju lika gärna dela med mig av min lösning:

För att frigöra videofilen behöver man göra som jag gjorde från början:

myMediaElement.Stop(); myMediaElement.Close(); myMediaElement.Source = null;

Dock släpper inte det filen direkt, utan det gör den någon gång strax därpå.
Att kalla på Garbage collectorn hjälper inte här heller.
Notera också att om .Stop() och .Close() ska fungera, så måste mediaelementet ha LoadedBehavior="Manual".

Så nu fungerar det ibland, om man tar det lugnt.
Mitt nästa steg var att om en fil-flytt misslyckades, så stoppar jag dess sökväg och destination i vars en Queue<string>. Efter varje flytt körs detta:

//Loopa igenom kön lika många gånger som den är lång, utan att iterera direkt i kön. //Misslyckas en flytt så stoppas den tillbaka sist i kön men den försöker inte flytta den igen denna gången. for (int i = 0; i < failedMovesFrom.Count; i++) { moveFile(failedMovesFrom.Dequeue(), failedMovesTo.Dequeue()); //.Dequeue returnerar och tar bort första värdet i kön. }

Om första flytten misslyckas så stoppas bilden i misslyckade-flyttar-kön, som den försöker flytta igen varje gång man flyttar en annan bild.

tl;dr: Om flytten misslyckas för att filen är låst, kom ihåg det och försök igen senare.