@@ -24,6 +24,7 @@ public partial class MainForm : Form
24
24
#region Start
25
25
26
26
private readonly Dictionary < string , object > _mainFormText = new ( ) ;
27
+ private CancellationTokenSource ? _mainCancellationTokenSource ;
27
28
28
29
private bool _textRecorded ;
29
30
@@ -517,73 +518,84 @@ private void fAQToolStripMenuItem_Click(object sender, EventArgs e)
517
518
#region ControlButton
518
519
519
520
private async void ControlButton_Click ( object ? sender , EventArgs ? e )
521
+ {
522
+ if ( ! IsWaiting ( ) )
520
523
{
521
- if ( ! IsWaiting ( ) )
522
- {
523
- await StopCoreAsync ( ) ;
524
- return ;
525
- }
524
+ await StopCoreAsync ( ) ;
525
+ return ;
526
+ }
526
527
527
- Configuration . SaveAsync ( ) . Forget ( ) ;
528
+ Configuration . SaveAsync ( ) . Forget ( ) ;
528
529
529
- // 服务器、模式 需选择
530
- if ( ServerComboBox . SelectedItem is not Server server )
531
- {
532
- MessageBoxX . Show ( i18N . Translate ( "Please select a server first" ) ) ;
533
- return ;
534
- }
530
+ // 服务器、模式 需选择
531
+ if ( ServerComboBox . SelectedItem is not Server server )
532
+ {
533
+ MessageBoxX . Show ( i18N . Translate ( "Please select a server first" ) ) ;
534
+ return ;
535
+ }
535
536
536
- if ( ModeComboBox . SelectedItem is not Mode mode )
537
- {
538
- MessageBoxX . Show ( i18N . Translate ( "Please select a mode first" ) ) ;
539
- return ;
540
- }
537
+ if ( ModeComboBox . SelectedItem is not Mode mode )
538
+ {
539
+ MessageBoxX . Show ( i18N . Translate ( "Please select a mode first" ) ) ;
540
+ return ;
541
+ }
541
542
542
- State = State . Starting ;
543
+ State = State . Starting ;
544
+
545
+ // Create new cancellation token for this session
546
+ _mainCancellationTokenSource = new CancellationTokenSource ( ) ;
547
+ var cancellationToken = _mainCancellationTokenSource . Token ;
543
548
544
- try
545
- {
546
- await MainController . StartAsync ( server , mode ) ;
547
- }
548
- catch ( Exception exception )
549
- {
550
- State = State . Stopped ;
551
- StatusText ( i18N . Translate ( "Start failed" ) ) ;
552
- MessageBoxX . Show ( exception . Message , LogLevel . ERROR ) ;
553
- return ;
554
- }
549
+ try
550
+ {
551
+ await MainController . StartAsync ( server , mode ) ;
552
+ }
553
+ catch ( Exception exception )
554
+ {
555
+ State = State . Stopped ;
556
+ StatusText ( i18N . Translate ( "Start failed" ) ) ;
557
+ MessageBoxX . Show ( exception . Message , LogLevel . ERROR ) ;
558
+ return ;
559
+ }
555
560
556
- State = State . Started ;
561
+ State = State . Started ;
557
562
558
- Task . Run ( Bandwidth . NetTraffic ) . Forget ( ) ;
559
- DiscoveryNatTypeAsync ( ) . Forget ( ) ;
560
- HttpConnectAsync ( ) . Forget ( ) ;
563
+ Task . Run ( ( ) => Bandwidth . NetTraffic ( cancellationToken ) , cancellationToken ) . Forget ( ) ;
564
+ DiscoveryNatTypeAsync ( ) . Forget ( ) ;
565
+ HttpConnectAsync ( ) . Forget ( ) ;
561
566
562
- if ( Global . Settings . MinimizeWhenStarted )
563
- Minimize ( ) ;
567
+ if ( Global . Settings . MinimizeWhenStarted )
568
+ Minimize ( ) ;
564
569
565
- // 自动检测延迟
566
- async Task StartedPingAsync ( )
570
+ // 自动检测延迟 - NOW WITH PROPER CANCELLATION
571
+ async Task StartedPingAsync ( )
572
+ {
573
+ try
567
574
{
568
- while ( State == State . Started )
575
+ while ( State == State . Started && ! cancellationToken . IsCancellationRequested )
569
576
{
570
577
if ( Global . Settings . StartedPingInterval >= 0 )
571
578
{
572
579
await server . PingAsync ( ) ;
573
580
ServerComboBox . Refresh ( ) ;
574
581
575
- await Task . Delay ( Global . Settings . StartedPingInterval * 1000 ) ;
582
+ await Task . Delay ( Global . Settings . StartedPingInterval * 1000 , cancellationToken ) ;
576
583
}
577
584
else
578
585
{
579
- await Task . Delay ( 5000 ) ;
586
+ await Task . Delay ( 5000 , cancellationToken ) ;
580
587
}
581
588
}
582
589
}
583
-
584
- StartedPingAsync ( ) . Forget ( ) ;
590
+ catch ( OperationCanceledException )
591
+ {
592
+ // Expected when shutting down
593
+ }
585
594
}
586
595
596
+ StartedPingAsync ( ) . Forget ( ) ;
597
+ }
598
+
587
599
#endregion
588
600
589
601
#region SettingsButton
@@ -1042,13 +1054,36 @@ public async Task StopAsync()
1042
1054
}
1043
1055
1044
1056
private async Task StopCoreAsync ( )
1057
+ {
1058
+ State = State . Stopping ;
1059
+
1060
+ // Cancel all background operations
1061
+ _mainCancellationTokenSource ? . Cancel ( ) ;
1062
+ _discoveryNatCts ? . Cancel ( ) ;
1063
+ _httpConnectCts ? . Cancel ( ) ;
1064
+
1065
+ try
1045
1066
{
1046
- State = State . Stopping ;
1047
- _discoveryNatCts ? . Cancel ( ) ;
1048
- _httpConnectCts ? . Cancel ( ) ;
1049
- await MainController . StopAsync ( ) ;
1067
+ // Add timeout to prevent infinite hang
1068
+ using var timeoutCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) ;
1069
+ await MainController . StopAsync ( ) . WaitAsync ( timeoutCts . Token ) ;
1070
+ }
1071
+ catch ( OperationCanceledException )
1072
+ {
1073
+ // Force stop if timeout reached
1074
+ Log . Warning ( "MainController.StopAsync() timed out, forcing termination" ) ;
1075
+ }
1076
+ catch ( Exception ex )
1077
+ {
1078
+ Log . Error ( ex , "Error during MainController.StopAsync()" ) ;
1079
+ }
1080
+ finally
1081
+ {
1082
+ _mainCancellationTokenSource ? . Dispose ( ) ;
1083
+ _mainCancellationTokenSource = null ;
1050
1084
State = State . Stopped ;
1051
1085
}
1086
+ }
1052
1087
1053
1088
private bool IsWaiting ( ) => IsWaiting ( _state ) ;
1054
1089
@@ -1274,31 +1309,39 @@ private void Minimize()
1274
1309
}
1275
1310
1276
1311
public async void Exit ( bool forceExit = false , bool saveConfiguration = true )
1312
+ {
1313
+ if ( ! IsWaiting ( ) && ! Global . Settings . StopWhenExited && ! forceExit )
1277
1314
{
1278
- if ( ! IsWaiting ( ) && ! Global . Settings . StopWhenExited && ! forceExit )
1279
- {
1280
- MessageBoxX . Show ( i18N . Translate ( "Please press Stop button first" ) ) ;
1281
-
1282
- ShowMainFormToolStripButton . PerformClick ( ) ;
1283
- return ;
1284
- }
1315
+ MessageBoxX . Show ( i18N . Translate ( "Please press Stop button first" ) ) ;
1316
+ ShowMainFormToolStripButton . PerformClick ( ) ;
1317
+ return ;
1318
+ }
1285
1319
1286
- // State = State.Terminating;
1287
- NotifyIcon . Visible = false ;
1288
- Hide ( ) ;
1320
+ // State = State.Terminating;
1321
+ NotifyIcon . Visible = false ;
1322
+ Hide ( ) ;
1289
1323
1290
- if ( saveConfiguration )
1291
- await Configuration . SaveAsync ( ) ;
1292
-
1293
- foreach ( var file in new [ ] { Constants . TempConfig , Constants . TempRouteFile } )
1294
- if ( File . Exists ( file ) )
1295
- File . Delete ( file ) ;
1324
+ if ( saveConfiguration )
1325
+ await Configuration . SaveAsync ( ) ;
1296
1326
1297
- await StopAsync ( ) ;
1327
+ foreach ( var file in new [ ] { Constants . TempConfig , Constants . TempRouteFile } )
1328
+ if ( File . Exists ( file ) )
1329
+ File . Delete ( file ) ;
1298
1330
1299
- Dispose ( ) ;
1300
- Environment . Exit ( Environment . ExitCode ) ;
1331
+ // ADD TIMEOUT HERE TOO
1332
+ try
1333
+ {
1334
+ using var exitTimeoutCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 5 ) ) ;
1335
+ await StopAsync ( ) . WaitAsync ( exitTimeoutCts . Token ) ;
1301
1336
}
1337
+ catch ( OperationCanceledException )
1338
+ {
1339
+ Log . Warning ( "StopAsync() timed out during exit, forcing termination" ) ;
1340
+ }
1341
+
1342
+ Dispose ( ) ;
1343
+ Environment . Exit ( Environment . ExitCode ) ;
1344
+ }
1302
1345
1303
1346
#region FormClosingButton
1304
1347
0 commit comments