Info: I'm creating game using C# in Visual Studio 2017
How can I stop music thread? Is it possible even from different form?
I used this code to create thread which plays music in background
MediaPlayer bg;
public void main()
{
IntializeComponent();
Bg_music();
}
private void Bg_music()
{
new System.Threading.Thread(() =>
{
bg = new System.Windows.Media.MediaPlayer();
bg.Open(new System.Uri(path + "Foniqz_-_Spectrum_Subdiffusion_Mix_real.wav"));
bg.Play();
}).Start();
}
When I try to stop the thread using this code, it stops window which is currently open and music/thread keeps playing music
bg.Dispatcher.Invoke(() =>
{
bg.Close();
});
also this didn't work
bg.Dispatcher.Invoke(() =>
{
bg.Stop();
});
Assuming you really need a background thread (because the MediaPlayer it's non-blocking on WPF) you may want to use one of the following paths in C#:
Use Cancelation Token & Tasks:
MediaPlayer bg;
readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
public MainWindow()
{
InitializeComponent();
Bg_music();
}
private void Bg_music()
{
Task.Run(() =>
{
bg = new MediaPlayer();
bg.Open(new Uri(#"D:\Songs\201145-Made_In_England__Elton_John__320.mp3"));
bg.Play();
bg.Play();
while (true)
{
if (tokenSource.Token.IsCancellationRequested)
{
bg.Stop();
break;
}
}
}, tokenSource.Token);
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
}
}
or
Use Events to communicate through Tasks. (Stop using threads, we have tasks now)
Cross-thread object access might be tricky.
Once you create MediaPlayer instance in another thread other than the UI thread, accessing this object inside the UI thread will throw InvalidOperationException since the object doesn't belong to UI thread.
private void Bg_music()
{
bg = new System.Windows.Media.MediaPlayer();
new System.Threading.Thread(() =>
{
bg.Dispatcher.Invoke(()=>{
bg.Open(new System.Uri(path + "Foniqz_-_Spectrum_Subdiffusion_Mix_real.wav"));
bg.Play();
});
}).Start();
}
Now you don't have to use Dispatcher to stop the MediaPlayer when calling it inside the UI thread.
Edit: Even if the implemented method is not the best practice, still worth to be answered to advert some theorical information.
Related
Currently I'm working on the WPF on .net core.
My application have to start Cef core to run the UI (instead of using WPF form).
Before of that I want to display a simple WPF window that say "Loading..."
So in the application start up, I have to start a thread like this
Thread thread = new Thread(() =>
{
try
{
DisplayLoader = true;
var f = new Loading();
f.Loaded += (a, b) =>
{
Task.Run(() =>
{
while (DisplayLoader)
Thread.Sleep(250);
f.Dispatcher.BeginInvoke(() =>
{
f.Close();
});
f.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate ()
{
f.Close();
}));
});
};
f.Show();
Dispatcher.Run();
}
catch
{
Loader.Close();
}
});
The point is, when the DisplayLoader become false, I saw the Dispatcher invoke the Close function too, however, nothing happened. I already follow a lot of answer on stackOverflow, but none of them works.
Below the thread start is this function, It will invoke Cef and display a Cef Window
thread.Start();
var assembly = Assembly.GetExecutingAssembly();
CefApp
.Run(assembly)
When the CefApp Loaded, the DisplayLoader will be set to false.
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
{
base.OnLoadEnd(browser, frame, httpStatusCode);
if (frame.IsValid)
{
if (App.DisplayLoader)
{
App.DisplayLoader = false;
}
}
}
EDIT:
The problem actually come from the CefAppBuilder, it embedded C++ code from CefGlue, then may cause some issue with C# function. Just do not modify any C# variable in C invoke function then it's fine.
The thread that executes a Window must be a UI thread which must be a STA thread.
You have to mark the thread as STA using Thread.SetApartmentState:
App.xaml
private void Run(object sender, StartupEventArgs e)
{
Thread uiThread = new Thread(DoWork);
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.IsBackground = true;
uiThread.Start();
}
But my recommended approach is to execute the initialization asynchronously. This avoids the overhead of creating additional UI threads and is also more compact in terms of lines of code and readability:
App.xaml
private TaskCompletionSource TaskCompletionSource { get; set; }
private async void Run(object sender, StartupEventArgs e)
{
Window splashScreen = new SplashScreenWindow();
splashScreen.Show();
await InitializeCefAppAsync();
splashScreen.Close();
}
private async Task InitializeCefAppAsync()
{
this.TaskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.LongRunning);
var assembly = Assembly.GetExecutingAssembly();
CefApp.Loaded += OnCefAppLoaded;
// Consider to implement an awaitable CefApp.InitializeAsync method
// instead of calling Run directly. This way you can remove the TaskCompletionSource pattern
CefApp.Run(assembly);
return this.TaskCompletionSource.Task;
}
private void OnLoaded(object sender, EventArgs e)
{
this.TaskCompletionSource.SetResult(true);
}
I created new Window on a different thread because it has heavy UI operations, that was done to keep my main window run smoothly. Everything works perfectly. But here is the question:
How I can access newly created window?
After calling Dispatcher.Run() I can not manipulate visualisationWindow anymore. I want to keep access to that newly created window object.
Here is how my window created:
private void CreateVisualisationWindow()
{
Thread VisualisationWIndowThread = new Thread(new ThreadStart(ThreadStartingPoint));
VisualisationWIndowThread.SetApartmentState(ApartmentState.STA);
VisualisationWIndowThread.IsBackground = true;
VisualisationWIndowThread.Start();
}
private void ThreadStartingPoint()
{
Visualisation visualisationWindow = new Visualisation();
visualisationWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
Also I tried accessing it through System.Windows.Threading.Dispatcher.FromThread(VisualisationWIndowThread) but seems I misunderstand some core things.
I simulated your issue using two WPF Window objects and a timer to ensure that the Second Window was created before calling operations on it. Below is my code sample and it updates the second Windows TextBox every five seconds:
private Timer _timer;
private SecondWindow _secondWindow;
public MainWindow()
{
InitializeComponent();
CreateVisualisationWindow();
_timer = new Timer(Callback);
_timer.Change(5000, 5000);
}
private void Callback(object state)
{
UpdateSecondWindowText();
}
private void CreateVisualisationWindow()
{
Thread VisualisationWIndowThread = new Thread(ThreadStartingPoint);
VisualisationWIndowThread.SetApartmentState(ApartmentState.STA);
VisualisationWIndowThread.IsBackground = true;
VisualisationWIndowThread.Start();
}
private void ThreadStartingPoint()
{
_secondWindow = new SecondWindow();
_secondWindow.SecondWindowTextBlock.Text = "Hello";
_secondWindow.Show();
Dispatcher.Run();
}
private void UpdateSecondWindowText()
{
_secondWindow.Dispatcher.BeginInvoke(new Action(() =>
{
_secondWindow.SecondWindowTextBlock.Text = _secondWindow.SecondWindowTextBlock.Text + " World";
}));
}
So the trick is, you need to call the Dispatcher on the second Window in order to gain access to it.
I have this class:
public class CursorWait : IDisposable
{
private readonly CancellationTokenSource _tokenSource;
public CursorWait(int showAfter)
{
_tokenSource = new CancellationTokenSource();
Task.Delay(showAfter, _tokenSource.Token).ContinueWith(delegate(Task task)
{
if (!task.IsCanceled)
Mouse.SetCursor(Cursors.Wait);
});
}
public void Dispose()
{
_tokenSource.Cancel();
Mouse.SetCursor(Cursors.Arrow);
}
}
To use it like this :
using (new CursorWait(showAfter: 500))
{
DoSomethingMayBeHeavyOrNotInUI();
}
However is not working since the Mouse.SetCursor relies in the UI thread to change it, and since it is busy, it will never change, so how can I change the cursor ?
Note: I know I should not be blocking the UI thread and instead just changing the property IsHitTestVisible of the window. but I'm new at this project and my team made the things this way, and they won't let me since the project is almost finished
Try adding this line After setting the mouse cursor.
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
Application.Current.Dispatcher.Invoke(new Action(()=>
{
// your code
}));
or
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new ThreadStart(delegate
{
// your code
}));
More information at: MSDN - Dispatcher.Invoke Method
I've been trying to learn more about asynchronous tasks and threading but not making a ton of headway.
I'm trying to load an "Engine" type of thread that will run in the background upon launch and be able to access the UI Thread to update variables, without hanging the UI Thread.
In the below code, Engine is called, and a Ticker object is created which holds the current value of (Litecoin/USD) called Last, also holds several other values that would be useful. This code successfully assigns the current value to label1.text. I don't necessarily need code but what approach would I take to create a ticker object in the background every second and update the UI thread with each new Ticker objects values.
Is this a good case for a background worker?
private void Form1_Load(object sender, EventArgs e)
{
Engine();
}
private void Engine()
{
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD:" + ltcusd.Last;
}
EDIT:
If I do the following, label1 throws an InvalidOperationException due to a Cross-thread operation attempt (label1 in the UI thread).
private void Form1_Load(object sender, EventArgs e)
{
var t = Task.Factory.StartNew(() => Engine());
t.Start();
}
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
}
Using async/await, the simplest way of getting an "asynchronous" sort of API is to invoke a new task. It's not great, but it'll make things simpler. I would probably create a new class which basically wrapped all the BtceApi methods in tasks:
public class BtceApiAsync
{
public Task<Ticker> GetTickerAsync(BtcePair pair)
{
return Task.Run(() => BtceApi.GetTicker(pair));
}
// etc
}
Then you can use a timer which fires once per second, which will start off a new task and update the UI appropriately:
// Keep a field of type System.Windows.Forms.Timer
timer = new Timer();
timer.Interval = 1000;
timer.Tick += DisplayTicker;
timer.Start();
...
private async void DisplayTicker(object sender, EventArgs e)
{
Ticker ticker = await BtceApiAsync.GetTickerAsync(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
Note that this doesn't mean the screen will be updated once per second... there will be a new task started once per second, and as soon as each task completes, the UI will be updated.
The use of await here - from an async method started on the UI thread - means you don't need to worry about using the UI; the whole async method will execute on the UI thread, even though the fetch itself happens in a different thread.
You can try ContinueWith to update the Label at the end of the task. If you want to update it event before the task ends then raise an event which is registered by on the UI thread. The event can then update the label.
I suppose this is Windows Forms. You could do it "old school style" and set the label text on the UI thread, and you can do that by passing delegate to the BeginInvoke or Invoke method.
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
UpdateText("LTC/USD: " + ltcusd.Last);
}
}
private void UpdateText(string text)
{
//Inspect if the method is executing on background thread
if (InvokeRequired)
{
//we are on background thread, use BeginInvoke to pass delegate to the UI thread
BeginInvoke(new Action(()=>UpdateText(text)));
}
else
{
//we are on UI thread, it's ok to change UI
label1.Text = text;
}
}
I connect to a webserive. While the webservice is connected i want to have a waiting form with an animated gif inside of it. The waiting form is correctly displayed but the animated give is not animated it is fixed.
Can anybody help me. I have already tried : DoEvents but the gif is still not animated.
// Create the new thread object
Thread NewThread = new Thread(new ThreadStart(RunThread));
// Start the new thread.
NewThread.Start();
// Inform everybody that the main thread is waiting
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
waitingDialog.Activate();
Application.DoEvents();
// Wait for NewThread to terminate.
NewThread.Join();
// And it's done.
waitingDialog.Close();
MessageBox.Show("Upload erfolgreich erledigt.", "Upload Erfolgreich",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
public void RunThread()
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
}
Don't call Join on a thread from within the UI thread. Instead, disable any controls you don't want to act on until the task has completed (e.g. buttons) and then call back into the UI thread when the operation has completed - so move the "And it's done" code into a new method which is invoked at the end of the operation. If you're using .NET 4, I'd suggest using the TPL for this, as it makes it easier to represent "a task which is in progress" and to add a continuation to it. (It's also a good start for what will become the idiomatic way of doing async operations in .NET 4.5.)
The problem is coming from your join. join is synchronous, so basically you are making your UI wait till the thread finishes its work.
You want to use a callback function to come back to your UI.
Edit : ive been skeetified
You problem is here:
NewThread.Join();
This blocks the UI thread until NewThread ends.
Here's one way to do it:
private myDelegate;
// ...
myDelegate = new Action(RunThread);
myDelegate.BeginInvoke(new AsyncCallback(MyCallback),null);
// You RunThread method is now running on a separate thread
// Open your wait form here
// ...
// This callback function will be called when you delegate ends
private void MyCallback(IAsyncResult ar)
{
myDelegate.EndInvoke(ar);
// Note this is still not the UI thread, so if you want to do something with the UI you'll need to do it on the UI thread.
// using either Control.Invoke (for WinForms) or Dispatcher.Invoke (for WPF)
}
Thread.Join is a blocking call that does not pump messages so that is your problem. It is typically advised to avoid calling any kind of synchronization mechanism that causes the UI thread to block.
Here is a solution using the Task class and the Invoke marshaling technique.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
Task.Factory.StartNew(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
waitingDialog.Invoke(
(Action)(() =>
{
waitingDialog.Close();
}));
});
}
Here is a solution using a raw Thread.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
var thread = new Thread(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
waitingDialog.Invoke(
(Action)(() =>
{
waitingDialog.Close();
}));
});
thread.Start();
}
C# 5.0 makes this kind of pattern even easier with its new async and await keywords1.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
await Task.Run(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
});
waitingDialog.Close();
}
1Not yet released.