Hello. Recently started learning C #. Before that, he worked in Delphi and in Qt (C ++). During a workout, I ran into a big problem that may seem very silly.

This is a very long story, I will try to describe everything briefly and accurately.
We consider three objects: 1) TextBox ; 2) MyClass with a timer inside; 3) the shape of the main window that encapsulates them.

MyClass starts a timer that generates an UpdateDuration event at intervals of about two times per second.

// Обработчик события таймера private void TickerCheck(Object source, System.Timers.ElapsedEventArgs e) { if (Animation) { Invalidate(); } if (Field.GameIsRunning) { if (Updater == TimerUpdateTicks) { Updater = 0; GameDuration = Field.RunningGameDuration; OnUpdateDuration(); } ++Updater; } } // внутренний обработчик события UpdateDuration protected void OnUpdateDuration() { UpdateDurationEventArgs ud = new UpdateDurationEventArgs(); EventHandler<UpdateDurationEventArgs> h = UpdateDuration; if (h != null) { ud.Duration = GameDuration; h(this, ud); } System.Diagnostics.Debug.WriteLine("Посылка отправлена: " + ud.Duration.ToString()); } 

The signers of the UpdateDuration event receive an argument containing inside the TimeSpan

 public class UpdateDurationEventArgs : EventArgs { private TimeSpan duration; public UpdateDurationEventArgs() { duration = TimeSpan.Zero; } public TimeSpan Duration { get { return duration; } set { duration = value; } } } 

I was going to use this TimeSpan to update the text in the TextBox. This textbox looks like this: Textbox I think you already understand what I want to do :) It should update the current game time. For the user, he plays the role of a stopwatch.

The signer of the UpdateDuration event is the form of the main window. It accepts an event and should (as planned) update the text inside the TextBox.

 void UpdateTimer(object sender, UpdateDurationEventArgs ud) { System.Diagnostics.Debug.WriteLine("Посылка получена: " + ud.Duration.ToString()); uint t_Seconds = (uint)ud.Duration.TotalSeconds, t_Minutes = t_Seconds/60, t_Hours = t_Minutes/60; t_Seconds = t_Seconds%60; t_Minutes = t_Minutes%60; t_Hours = t_Hours %100; string newText = t_Hours.ToString("D2") + ':' + t_Minutes.ToString("D2") + ':' + t_Seconds.ToString("D2"); textBox_timer.Text = newText; // вот здесь она должна обновлять его System.Diagnostics.Debug.WriteLine("Посылка распакована: " + ud.Duration.ToString()); } 

But! It was not there. Does anyone already see the error? Not? I do not see: T
When I started the timer, TextBox always remained in zeros. Then I created a button and tried to start the event handler manually:

 void Button_Rec_Click(object sender, System.EventArgs e) { UpdateDurationEventArgs ud = new UpdateDurationEventArgs(); ud.Duration = System.TimeSpan.FromSeconds(new System.Random().Next(8000)); UpdateTimer(this, ud); } 

And now, on the button, everything works fine: in TextBox, randomly exhibited different times. Before that, I have not tried to catch exceptions in C #. Rummaged in MSDN and did this:

 void UpdateTimer(object sender, UpdateDurationEventArgs ud) { System.Diagnostics.Debug.WriteLine("Посылка получена: " + ud.Duration.ToString()); uint t_Seconds = (uint)ud.Duration.TotalSeconds, t_Minutes = t_Seconds/60, t_Hours = t_Minutes/60; t_Seconds = t_Seconds%60; t_Minutes = t_Minutes%60; t_Hours = t_Hours %100; string newText = t_Hours.ToString("D2") + ':' + t_Minutes.ToString("D2") + ':' + t_Seconds.ToString("D2"); try { // добавил блок try catch textBox_timer.ResetText(); // и подставил строчки для дебага System.Diagnostics.Debug.WriteLine(" resetText"); textBox_timer.Text = newText; System.Diagnostics.Debug.WriteLine(" newText"); } catch (System.Exception) { System.Diagnostics.Debug.WriteLine("Звездец, граждане!"); } System.Diagnostics.Debug.WriteLine("Посылка распакована: " + ud.Duration.ToString()); } 

What did I see?

Star, citizens!

Star Frustration in its purest form. The parcel is sent (an event is generated, see the OnUpdateDuration code), it is accepted by the signer, but an exception is thrown, even when the text is reset . At the same time, by the same button, manually, everything works fine:

Works great

Do you already see the error? What have I done wrong? : T
I called this poor button to death (more than 10 clicks per second), but the "star" did not come out to the console - the processing goes well.

Here you have the right to assume that MyClass sends some kind of invalid TimeSpan. I dare to assure that this is not at all the case: for the same timer, the handler outputs time to the debug console without any problems:

Works great

An interesting point: I made it so that when the timer was manually stopped, MyClass sent the UpdateDuration event one more time, after the timer was stopped, to send the timers to zero TimeSpan.Zero . So I intended to reset the time on Textbox. So, this final event is also accepted without any problems.

And why is this happening?

Funny, but if you replace the textbox with a label, the situation does not change. And only if you use a timer directly belonging to the form , everything works well. But does a timer working in another class violate some principles of OOP? After all, MyClass objects, form and TextBox are all in one main thread! I'm at a loss.

The program does what the programmer wrote, not what he wanted. Where is my mistake? I ditched yesterday a few hours in search of a solution, before it cuts in my eyes. Even put the handler in the mutex, although it seems to be useless here. Help me please.

WindowsXP, NetFramework 4, SharpDevelop.
You have earned an achievement! You have read my question completely! Thank :)

  • one
    I did not finish reading the question, but Wang, that you are accessing the UI controls from the sidestream in which messages from the timer arrive. - VladD
  • And yes, WPF or WinForms? - VladD
  • @VladD Honestly, I don’t know) It seems that WPF uses XAML, but I don’t use it. And if so, then it turns out that this is WinForms - Denis
  • one
    Denis, you are well done! Your question is an example of how a person tries to understand the problem himself, makes considerable efforts for this, and asks a question that includes a description of these attempts. I want to answer such questions! I think @VladD will agree with me. - Igor
  • @Igor Thank you. It was also nice to write this question, I really spoke about the painful one) I will keep this in mind in the future - Denis

1 answer 1

You did not write which of the many timers available in .NET and its frameworks you use. Some of them deliver messages in a UI stream, some in a background thread. (And some differently, depending on how you configured them.)

Apparently, you chose a timer that delivers messages in the background thread. In this case, an attempt to access the controls from the timer code leads to an exception: it is impossible to work with the controls from background threads.

What are the solutions?

  1. Take the appropriate timer. For example, if you are writing a Winforms application, System.Windows.Forms.Timer will do.
  2. If for some very valid reasons you cannot use a suitable timer (“well, I’ve tried the type, but the karoch doesn’t work” is not considered a good reason), then you will have to smashall the call into the UI flow. For Winforms, this is done like this:

     if (textBox_timer.InvokeRequired) { textBox_timer.Invoke((MethodInvoker)(() => { textBox_timer.Text = что-то; })); } else { textBox_timer.Text = что-то; } 
  • Yes exactly! I completely forgot. True, I used a timer from System.Timers. And the timer that “works fine” was from System.Windows.Forms - Denis
  • So, when the time comes to trigger an event, does the timer from the background thread call My Class and its handler runs from the background thread's event loop? - Denis
  • @ Denis: Timer starts the event in the background thread. Since your class is subscribed to this event, it receives it. In the thread in which this event runs. - VladD
  • one
    , empty brackets mean the absence of parameters - Andrew NOP
  • one
    @ Andrey Thank you! Now I study the lambda expressions xD what a cool this NET - Denis