Permalänk

Hjälp med trådning i C#

Hejsan!

Jag har ägnat lite tid åt att skriva en bildvisare som ska preloada bilder medans man kollar på en bild. För att detta ska funka som tänkt så måste ju inladdningen av bilder i bakgrunden, köras i en egen tråd. Jag har lagt in så att metoden Preload körs i en egen tråd. Men när preload körs i en egen tråd, så skiter det sig när jag byter bild (se felmeddelande http://piclair.com/data/cag9c.jpg).

Jag lägger upp den kod som jag tänker är relevant för detta.

List<image> images = new List<image>(); void switchimage(int images1 = 1) { try { if (currentindex + images1 >= 0 && currentindex + images1 < images.Count()) { images[currentindex + images1].loadimage(); image1.Source = images[currentindex + images1].img; currentindex += images1; TransformGroup transformGroup = (TransformGroup)image1.RenderTransform; ScaleTransform transform = (ScaleTransform)transformGroup.Children[0]; transform.ScaleX = 1; transform.ScaleY = 1; } else { //Gör något när det är slut på bilder } } catch (Exception e) { MessageBox.Show(e.ToString()); } } void preload() { for (int i = currentindex + 1; i < currentindex + 5; i++) { images[i].loadimage(); } } class image { public static double CenterX { get; set; } public static double CenterY { get; set; } private Uri src; public BitmapImage img { get; private set; } public string filepath { get; private set; } public bool loaded; public image(string filepath, bool preload = false) { try { this.filepath = filepath; if (preload) { loadimage(); } } catch (Exception e) { } } public void loadimage() { if (!loaded) { src = new Uri(@filepath); img = new BitmapImage(); img.BeginInit(); img.UriSource = src; img.CacheOption = BitmapCacheOption.OnLoad; img.EndInit(); image.CenterX = img.Width / 2; image.CenterY = img.Height / 2; loaded = true; } } public string getDirectory() { return Path.GetDirectoryName(filepath); } public string filename() { return Path.GetFileName(filepath); } }

Permalänk

Kika lite på propertyn "InvokeRequired" och funktionen "Invoke()"

Visa signatur

Asus Striker II Extreme / XFX Geforce GTX 280 / Q9450 @ 3.6GHz/ TRUE Noctua 120/ 4x1GB Corsair TWIN3X2048-1333C9DHX / X25-M G2 80gb Velociraptor / Win 7 Ultimate x64/ Antec P190

MovieDatabase

Permalänk
Medlem

En annan tråd än huvudtråden kan inte operera på t ex grafiska objekt som hör till huvudtråden.
Anropet måste ske via delegater. Som anropas genom tex en Invoke. Skriver på mobilen nu. Därför kortfattat svar.

Visa signatur

Lill-server(2010): SFF NAS Zotac H55ITX-C-E, Lian Li PC-Q08B, Intel Core i3 540
Stor-server(2014): SuperMicro X10SL7-F, 20GB ECC RAM, 4x2TB WD Green, E3-1230v3, 2xIntel Dual Gigabit Nic

Permalänk
Medlem

håller med om invoke, löser säkerligen problemet.

Swec på mobil, hm vanlig webläsare?
skulle vilja ha nån app som kunde sköta allt lite bättre, kör wm6.1 på min smått antika touch pro.

Permalänk

Har kikat lite på MSDN och vad google gav mig, men jag förstår inte riktigt hur jag ska implementera det. Ingen som har lust att visa?

Permalänk
Medlem

Jo då. 08:30,om ingen annan hinner före, imorgon skriver jag ett exempel som uppdaterar en label i en form från en annan tråd.
Jobbigt att skriva från en Htc hd2 just nu.

Visa signatur

Lill-server(2010): SFF NAS Zotac H55ITX-C-E, Lian Li PC-Q08B, Intel Core i3 540
Stor-server(2014): SuperMicro X10SL7-F, 20GB ECC RAM, 4x2TB WD Green, E3-1230v3, 2xIntel Dual Gigabit Nic

Permalänk
Hedersmedlem
Skrivet av Gildebrand:

Har kikat lite på MSDN och vad google gav mig, men jag förstår inte riktigt hur jag ska implementera det. Ingen som har lust att visa?

Om man till exempel från en bakgrundstråd vill ändra label1 kan man köra

label1.Invoke(new MethodInvoker(delegate() { label1.Text = "ujuj"; }));

istället för bara

label1.Text = "ujuj";

Permalänk
Medlem

Här är fungerande kod(som används av mig i ett projekt för en kund...).
Koden uppdaterar en label. lbl_whatnumber med en text. Oavsett om den anropas från huvudtråd eller från annan tråd. Nästa programmerare som tar över behöver inte tänka på om han måste köra Invoke eller inte... Bara anropa koden med

AddTextToLabel("villskrivadetta");

public delegate void AddTextToLabelDelegate(String text); //Delegat som kan anropa på huvudtråd. public void AddTextToLabel(String text) { try { if (this.lbl_whatnumber.InvokeRequired) //Måste vi köra en Invoke? { this.Invoke(new AddTextToLabelDelegate(AddTextToLabel), text); //Japp, med argumentet text. } else { this.lbl_whatnumber.Text = text; } } catch (Exception ex) { MessageBox.Show(ex.InnerException.Message); } }

Hoppas det gav lite klarhet. Finns andra sätt att göra det på, men det här är ganska enkelt.
Jag använder samma modell för att ändra properties i en progressbar i runtime.

MVH
Magnus

Visa signatur

Lill-server(2010): SFF NAS Zotac H55ITX-C-E, Lian Li PC-Q08B, Intel Core i3 540
Stor-server(2014): SuperMicro X10SL7-F, 20GB ECC RAM, 4x2TB WD Green, E3-1230v3, 2xIntel Dual Gigabit Nic

Permalänk
Medlem

Blev sugen på att snygga till det hela och komma med en enklare lösning att använda, slut resultatet använde sig av Extension Methods, Action<T> Delegate och Generics

Notera att implementeringen gäller för samtliga kontroller, oavsett om det är en textbox, label, dropdown etc. Så länge de ärver från Control klassen så fungerar det, vilket samtliga gör.

Windows Forms version, för WPF se senare post.
InvokeExtensionMethods.cs, notera namespace. Detta är viktigt.

namespace System.Windows.Forms { public static class InvokeExtensionMethods { /// <summary> /// Executes the specified action on the thread that owns the control's underlying // window handle. /// </summary> /// <param name="control">The control</param> /// <param name="action">The action</param> public static void Invoke(this Control control, Action action) { if (control.InvokeRequired) control.Invoke(action); else action(); } /// <summary> /// Executes the specified action on the thread that owns the control's underlying // window handle. /// </summary> /// <typeparam name="T">A control type</typeparam> /// <param name="control">The control</param> /// <param name="action">The action</param> public static void Invoke<T>(this T control, Action<T> action) where T : Control { if (control.InvokeRequired) control.Invoke(action, control); else action(control); } /// <summary> /// Sets the text on the control with the thread that owns the control's underlying // window handle. /// </summary> /// <param name="control">The control</param> /// <param name="text">The new Text you want to set</param> public static void SetText(this Control control, string text) { control.Invoke(item => item.Text = text); } } }

Hur det används

Ex 1: Anonymous Action, with parameter
Notera att du kan göra flera saker samtidigt

label1.Invoke(item => { item.Text = "Test 1, with parameter"; item.BackColor = Color.Red; });

Ex 1: Anonymous Action
Notera att du kan göra flera saker samtidigt

label1.Invoke(() => { label1.Text = "Test 2, no paramter"; label1.BackColor = Color.Red; });

Ex 3: Ny sett text metod.
Denna metod är enbart till för att sätta texten på en kontroll

label1.SetText("This is thread safe");

Det hela är väldigt mycket i stil med Elgot lösning, men jag tyckte att den var lite för krånglig (se de understrukna) samt att den fungerar både om du är på GUI tråden eller om det anropas från någon annan.

label1.Invoke(new MethodInvoker(delegate() { label1.Text = "ujuj"; }));

Visa signatur

CPU: i7 6700k + Fractal Design S24 GPU: ASUS GeForce GTX 1070 8GB DUAL OC RAM: Kingston 16GB 2133MHz CL13 MB: MSI GAMING M7 PSU: EVGA Supernova G2 850W, 80+ Gold SSD: Samsung SM951 256GB M.2 NVMe + Samsung EVO 850 250GB M.2 Chassi: Fractal Design S Skrämar: Acer XB270HU + 2x Dell U2412M
NAS: Synology DS415+ (4x WD RED 6 TB) Console: Xbox One

Permalänk
Hedersmedlem
Skrivet av Tino:

Det hela är väldigt mycket i stil med Elgot lösning, men jag tyckte att den var lite för krånglig (se de understrukna) samt att den fungerar både om du är på GUI tråden eller om det anropas från någon annan.

Bara för att förtydliga påpekar jag att även den lösning jag tipsade om fungerar oavsett varifrån den anropas.

Permalänk

Jag tackar och bockar för alla era fina exempel. Känner dock att jag missa en liten detalj. Jag jobbar i WPF. Jag lyckas inte ens få fram metoden invoke, finns inte med. Har jag missat något namespace?

Permalänk
Hedersmedlem
Skrivet av Gildebrand:

Jag tackar och bockar för alla era fina exempel. Känner dock att jag missa en liten detalj. Jag jobbar i WPF. Jag lyckas inte ens få fram metoden invoke, finns inte med. Har jag missat något namespace?

Det verkar som att man istället för InvokeRequired kan använda !Dispatcher.CheckAccess
Om man lånar Dockers version blir det kanske såhär:

public delegate void AddTextToLabelDelegate(String text); //Delegat som kan anropa på huvudtråd. public void AddTextToLabel(String text) { try { if (!Dispatcher.CheckAccess()) //Måste vi köra en Invoke? { Dispatcher.Invoke(new AddTextToLabelDelegate(AddTextToLabel), text); //Japp, med argumentet text. } else { this.lbl_whatnumber.Content = text; } } catch (Exception ex) { MessageBox.Show(ex.InnerException.Message); } }

Permalänk
Medlem
Skrivet av Elgot:

Bara för att förtydliga påpekar jag att även den lösning jag tipsade om fungerar oavsett varifrån den anropas.

Får följande fel om jag kör din kod i Form1() konstruktorn

System.InvalidOperationException was unhandled Message="Invoke or BeginInvoke cannot be called on a control until the window handle has been created." Source="System.Windows.Forms" StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at FormsInvokeTest.Form1..ctor() in C:\Users\[...]\FormsInvokeTest\Form1.cs:line 23 at FormsInvokeTest.Program.Main() in C:\Users\[...]\FormsInvokeTest\FormsInvokeTest\Program.cs:line 18 at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:

Skall se vad jag kan göra för WPF

Visa signatur

CPU: i7 6700k + Fractal Design S24 GPU: ASUS GeForce GTX 1070 8GB DUAL OC RAM: Kingston 16GB 2133MHz CL13 MB: MSI GAMING M7 PSU: EVGA Supernova G2 850W, 80+ Gold SSD: Samsung SM951 256GB M.2 NVMe + Samsung EVO 850 250GB M.2 Chassi: Fractal Design S Skrämar: Acer XB270HU + 2x Dell U2412M
NAS: Synology DS415+ (4x WD RED 6 TB) Console: Xbox One

Permalänk
Hedersmedlem
Skrivet av Tino:

Får följande fel om jag kör din kod i Form1() konstruktorn

Hehe, ja just där är det kanske lite jobbigt.

Permalänk
Medlem

WPF Version, notera att ni använder den på samma set som Forms versionen (se tidigare post)
InvokeExtensionMethods.cs, notera namespace. Detta är viktigt.

namespace System.Windows.Controls { public static class InvokeExtensionMethods { /// <summary> /// Executes the specified action on the thread that owns the control's underlying // window handle. /// </summary> /// <param name="control">The control</param> /// <param name="action">The action</param> public static void Invoke(this DispatcherObject control, Action action) { if (!control.Dispatcher.CheckAccess()) control.Dispatcher.Invoke(action); else action(); } /// <summary> /// Executes the specified action on the thread that owns the control's underlying // window handle. /// </summary> /// <typeparam name="T">A control type</typeparam> /// <param name="control">The control</param> /// <param name="action">The action</param> public static void Invoke<T>(this T control, Action<T> action) where T : DispatcherObject { if (!control.Dispatcher.CheckAccess()) control.Dispatcher.Invoke(action, control); else action(control); } /// <summary> /// Sets the text on the control with the thread that owns the control's underlying // window handle. /// </summary> /// <param name="control">The control</param> /// <param name="text">The new Text you want to set</param> public static void SetText(this Label control, string text) { control.Invoke(item => control.Content = text); } /// <summary> /// Sets the text on the control with the thread that owns the control's underlying // window handle. /// </summary> /// <param name="control">The control</param> /// <param name="text">The new Text you want to set</param> public static void SetText(this TextBlock control, string text) { control.Invoke(item => control.Text = text ); } /// <summary> /// Sets the text on the control with the thread that owns the control's underlying // window handle. /// </summary> /// <param name="control">The control</param> /// <param name="text">The new Text you want to set</param> public static void SetText<T>(this T control, string text) where T : DispatcherObject, IAddChild { control.Invoke(item => control.AddText(text)); } } }

Hur det används

Ex 1: Anonymous Action, with parameter
Notera att du kan göra flera saker samtidigt

label1.Invoke(item => { item.Content = "Test 1, with parameter"; item.Background = new SolidColorBrush(Colors.Red); });

Ex 2: Anonymous Action
Notera att du kan göra flera saker samtidigt

label1.Invoke(() => { label1.Content = "Test 2, no paramter"; label1.Background = new SolidColorBrush(Colors.Blue); });

Ex 3: Ny sett text metod.
Denna metod är enbart till för att sätta texten på en kontroll

label1.SetText("This is thread safe");

Edit: Var tvungen att hantera TextBlock, Label separat, därav flera SetText metoder. Saknar ITextControl från ASP.Net

Edit 2: Synd att jag inte har .NET 4 installerat. Kunde göra en snygg hantering med hjälp av dynamic. Kanske kan skriva den på jobbet på måndag. Dock så kommer den lösningen kräva .NET 4.

Edit 3: Gillar dock DispatcherObject vilket gör att våra Invoke metoder fungerar för fler kontroller (inte bra Controls).

Visa signatur

CPU: i7 6700k + Fractal Design S24 GPU: ASUS GeForce GTX 1070 8GB DUAL OC RAM: Kingston 16GB 2133MHz CL13 MB: MSI GAMING M7 PSU: EVGA Supernova G2 850W, 80+ Gold SSD: Samsung SM951 256GB M.2 NVMe + Samsung EVO 850 250GB M.2 Chassi: Fractal Design S Skrämar: Acer XB270HU + 2x Dell U2412M
NAS: Synology DS415+ (4x WD RED 6 TB) Console: Xbox One

Permalänk

Försöker få till det med den koden du postade, men CheckAccess finns inte, och då har jag lagt med System.Windows.Threading.

Permalänk
Hedersmedlem
Skrivet av Gildebrand:

Försöker få till det med den koden du postade, men CheckAccess finns inte, och då har jag lagt med System.Windows.Threading.

Är du säker? Hos mig syns inte CheckAccess i intellisense-listan, men den går bra att kompilera i alla fall.

Permalänk
Medlem
Skrivet av Gildebrand:

Försöker få till det med den koden du postade, men CheckAccess finns inte, och då har jag lagt med System.Windows.Threading.

Den finns men som Elgot säger så syns inte CheckAccess i intellisense-listan, men den går bra att kompilera i alla fall.

om du vill kan du använda dig av följande istället:

if (control.Dispatcher.Thread != System.Threading.Thread.CurrentThread)

Visa signatur

CPU: i7 6700k + Fractal Design S24 GPU: ASUS GeForce GTX 1070 8GB DUAL OC RAM: Kingston 16GB 2133MHz CL13 MB: MSI GAMING M7 PSU: EVGA Supernova G2 850W, 80+ Gold SSD: Samsung SM951 256GB M.2 NVMe + Samsung EVO 850 250GB M.2 Chassi: Fractal Design S Skrämar: Acer XB270HU + 2x Dell U2412M
NAS: Synology DS415+ (4x WD RED 6 TB) Console: Xbox One

Permalänk

Jäklar vad mycke nytt jag fick lära mig nu då
Får tyvärr fortfarande samma fel
Ska försöka få upp all relevant kod.

namespace System.Windows.Controls { public static class InvokeExtensionMethods { public static void Invoke(this DispatcherObject control, Action action) { if (!control.Dispatcher.CheckAccess()) control.Dispatcher.Invoke(action); else action(); } public static void Invoke<T>(this T control, Action<T> action) where T : DispatcherObject { if (!control.Dispatcher.CheckAccess()) control.Dispatcher.Invoke(action, control); else action(control); } public static void SetImage(this Image control, BitmapImage bmp) { control.Invoke(item => control.Source = bmp); } } }

List<image> images = new List<image>(); string[] args; int currentindex; public MainWindow() { InitializeComponent(); args = Environment.GetCommandLineArgs(); } void PreLoad() { for (int i = currentindex + 1; i < currentindex + 5; i++) { images[i].loadimage(); } } public void SwitchImage(int images1 = 1) { try { if (currentindex + images1 >= 0 && currentindex + images1 < images.Count()) { images[currentindex + images1].loadimage(); image1.Invoke(item => { item.Source = images[currentindex + images1].img; }); currentindex += images1; TransformGroup transformGroup = (TransformGroup)image1.RenderTransform; ScaleTransform transform = (ScaleTransform)transformGroup.Children[0]; transform.ScaleX = 1; transform.ScaleY = 1; } else { //Gör något när det är slut på bilder } } private void Window_Loaded(object sender, RoutedEventArgs e) { DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(args[1])); FileInfo[] filer = dir.GetFiles("*.jpg"); foreach (FileInfo fil in filer) { images.Add(new image(fil.FullName)); } images.Sort(new collectionsorter()); images.Reverse(); currentindex = images.FindIndex(FindFile); image1.Source = new image(args[1], true).img; Thread thrPreLoad = new Thread(new ThreadStart(PreLoad)); thrPreLoad.Start(); }

Permalänk
Medlem

Problemet som du får är att du försöker hämta data från images via UI tråden. Problemet är dock att images skapades av en annan tråd och du får detta problem. Min lösning hanterar det omvända problemet.

Jag har försökt hitta en lösning men har ej kommit fram till någon än.

Visa signatur

CPU: i7 6700k + Fractal Design S24 GPU: ASUS GeForce GTX 1070 8GB DUAL OC RAM: Kingston 16GB 2133MHz CL13 MB: MSI GAMING M7 PSU: EVGA Supernova G2 850W, 80+ Gold SSD: Samsung SM951 256GB M.2 NVMe + Samsung EVO 850 250GB M.2 Chassi: Fractal Design S Skrämar: Acer XB270HU + 2x Dell U2412M
NAS: Synology DS415+ (4x WD RED 6 TB) Console: Xbox One

Permalänk

Men skulle det då funka om jag använde invoke på loopen som preloadar bilderna?

Permalänk
Hedersmedlem
Skrivet av Gildebrand:

Men skulle det då funka om jag använde invoke på loopen som preloadar bilderna?

Det är nog det mera normala sättet; använd bakgrundstrådar för att samla in och bearbeta data och skicka sedan resultaten till gui-tråden med hjälp av invoke eller liknande.

Permalänk

Måste inte klassen InvokeExtensionMethods skrivas om då, för att funka?

Det är alltså metoden PreLoad som körs i en egen tråd, och som ska kunna pilla på objekt utan att den tråden "tar över" objektet.

void PreLoad() { for (int i = currentindex + 1; i < currentindex + 5; i++) { images[i].loadimage(); }

Hur blir det att köra invoke på images[i].loadimage?

Permalänk
Hedersmedlem
Skrivet av Gildebrand:

Måste inte klassen InvokeExtensionMethods skrivas om då, för att funka?

Jo, du får till exempel ha images-listan i huvudtråden och använda invoke från övriga trådar för att modifiera innehållet.

Permalänk

Det är ju alltså PreLoad som ställer till det när den börjar på att ladda in bilder i arrayet. Det jag måste göra är alltså att köra invoke på "images[i].loadimage()" biten, så att den inte tar över arrayet, eller? Hur gör jag det?

Permalänk
Hedersmedlem
Skrivet av Gildebrand:

Det jag måste göra är alltså att köra invoke på "images[i].loadimage()" biten, så att den inte tar över arrayet, eller? Hur gör jag det?

Man borde väl till exempel kunna sköta inläsningen till ett Image-objekt i bakgrunden och sedan, när denna är färdig, med hjälp av invoke skicka resultatet till huvudtråden och där lägga det på rätt plats i images-fältet?

Permalänk

Hur ser det ut i "praktiken"?