This picture is my error about STA thread.
- Project is waiting incoming data. Actually I don't want to just wait for the application. and I'm try with dispatcher or Task but I take this error.
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
ToggleStart();
var stackPanel = await Task.Run(TaskCheckBoxZones).ConfigureAwait(false);
ZonesBorder.Child = stackPanel;
//await Dispatcher.BeginInvoke((Action)async delegate
//{
// var findCheckBoxes = new FindCheckBoxes
// {
// CheckBoxItemTab = CheckBoxPanel,
// CheckBoxItemTab2 = CheckBoxPanel2,
// Token = token,
// };
// await findCheckBoxes.LoadPaymentsMethods();
//});
}
async Task<StackPanel> TaskCheckBoxZones()
{
var stackPanel = await new CreateCheckBoxZones()
{
Token = token
}.LoadZonesData();
return stackPanel;
}
Above code block in Settings.xaml.cs
public class CreateCheckBoxZones
{
private List<OrderZones> OrderZonesList;
public string Token = "";
public CreateCheckBoxZones()
{
OrderZonesList = new List<OrderZones>();
_innerStack = new StackPanel();
}
//public Border Border;
private StackPanel _innerStack;
public StackPanel InnerStack
{
get { return _innerStack; }
set { _innerStack = value; }
}
public async Task<StackPanel> LoadZonesData()
{
OrderZonesList = await MapOrderZones();
foreach (var zone in OrderZonesList)
{
CheckBox cb = new CheckBox();
cb.Tag = zone.Id;
cb.Content = zone.Name;
cb.IsChecked = zone.Status == 100;
cb.Click = ZonesCheckBox_Click;
_innerStack.Children.Add(cb);
}
return _innerStack;
}
private async Task<List<OrderZones>> MapOrderZones()
{
var zoneModels = await TakeZones();
if (zoneModels[0]._id == null)
throw new Exception("Zones is can not null.");
foreach (var zone in zoneModels)
{
OrderZonesList.Add(new OrderZones(
zone._id,
zone.placement,
zone.name.tr,
zone.minBasketSize,
zone.status,
zone.restaurant));
}
return OrderZonesList;
}
public async Task<List<ZoneModel>> TakeZones()
{
try
{
string url = Constants.GET_ZONES;
var response = await new WebClient<List<ZoneModel>>()
{
Endpoint = url,
Token = Token,
Method = Method.GET
}.SendToAPIAsync();
if (response.ErrorException != null)
{
MessageBox.Show("Bölgeleri alırken, bir hata aldın.", "Bölgeler", MessageBoxButton.OK, MessageBoxImage.Error);
}
return response.Data;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
above code block is about my LoadCheckbox class this class getting some API information with endpoints i take data but. My application is freezing during while take Define checkbox on UI. i am searching some solve think.
CodePudding user response:
You should make use of a ListBox
(instead of a StackPanel
) and a DataTemplate
to generate the items (including the CheckBox
).
- NEVER wrap an
async
Method into aTask.Run
! SinceTaskCheckBoxZones
is anasync
method, theTask.Run(TaskCheckBoxZones)
is a no-no.TaskCheckBoxZones
or async methods in general should be named with the 'Async' suffix e.g.TaskCheckBoxZonesAsync
. This will help you to recognize asynchronous methods to handle them correctly. - You are using
ConfigureAwait(false)
wrong.ConfigureAwait(false)
is meant to avoid a context switch back to the capturedSynchronizationContext
by allowing the continuation to execute on a thread pool thread. Fore example, when aTask
captures the currentSynchronizationContext
of the main thread, then configuring it usingConfigureAwait(false)
will likely force the continuation i.e. the code after theawait
, to execute on a non UI thread.
UsingConfigureAwait(false)
in a UI context is very likely to throwInvalidOperationException
to indicate an illegal cross-thread operation:
private void onl oaded(object sender, EventArgs e)
{
/** UI thread context **/
// Task captures the current SynchronizationContext which is the UI thread.
var stackPanel = await Task.Run(TaskCheckBoxZones) // Never wrap an async method into a Task.Run
.ConfigureAwait(false); // ConfigureAwait(false) will force continuation on a non UI thread.
/** Worker thread context (non UI) **/
ZonesBorder.Child = stackPanel; // Will throw InvalidOperationException (cross-thread)
To fix this, you should generally never use ConfigureAwait(false)
in the UI context. It is meant for non UI library code. On UI level or in an application level context you very likely want to return to the UI thread after the async
operation has completed:
private void onl oaded(object sender, EventArgs e)
{
/** UI thread context **/
// Task captures the current SynchronizationContext which is the UI thread.
// Since ConfigureAwait(true) is the default behavior, the Task will return to the UI thread.
var stackPanel = await TaskCheckBoxZonesAsync();
/** UI thread context **/
ZonesBorder.Child = stackPanel; // Access allowed
- Don't use
Dispatcher.BeginInvoke
. It's and ancient method, part of the pre async/await API era. Since .NET Framework 4.5 we useDispatcher.InvokeAsync
. - Don't enqueue code that executes on the UI thread to the
Dispatcher
. Additionally, awaiting theDispatcher
in this scenario is absolutely redundant. Rule 1) also applies to theDispatcher
: avoid wrapping anasync
method into aDispatcher
call:
Your bad version
await Dispatcher.BeginInvoke((Action)async delegate
{
var findCheckBoxes = new FindCheckBoxes
{
CheckBoxItemTab = CheckBoxPanel,
CheckBoxItemTab2 = CheckBoxPanel2,
Token = token,
};
await findCheckBoxes.LoadPaymentsMethods();
});
The correct version
var findCheckBoxes = new FindCheckBoxes
{
CheckBoxItemTab = CheckBoxPanel,
CheckBoxItemTab2 = CheckBoxPanel2,
Token = token,
};
await findCheckBoxes.LoadPaymentsMethodsAsync();
- Don't create a background thread only to create a control on the
Dispatcher
. This is absolutely pointless and expensive. If instantiation of your control is expensive consider to use anasync
initialization routine. Generally calling a constructor on a background thread is a serious code smell and an indicator of a poorly written class. A constructor must return immediately. A constructor should never be long-running:
Your bad version
private asnyc void OnButton_Click(object sender, EventArgs e)
{
await Task.Run(() => Dispatcher.InvokeAsync(new MyDialogWindow().ShowDialog));
}
The correct version
private asnyc void OnButton_Click(object sender, EventArgs e)
{
var dialog = new MyDialogWindow();
await dialog.InitializeAsync(); // Perform longrunning and CPU intensive or IO initialization in a non blocking manner
dialog.ShowDialog();
}
- There is no reason to let a
private
method return a member (e.g., a property). Just make it returnvoid
. - Never catch the base class
Exception
and never show exception messages to the user. If you can recover from a particular exception, then only catch this one. Let the application crash on any other exception.
Solution
MainWindow.xaml.cs
partial class MainWIndow : Window
{
// Use Clear, Add and Remove to modify this collection during runtime.
// If you need to replace the complete collection (not recommended), then make this property into a dependency property.
public ObservableCollection<OrderZone> OrderZones { get; }
public MainWindow()
{
InitializeComponent();
this.ZoneModels = new ObservableCollection<OrderZone>();
this.Loaded = onl oaded;
}
private async void onl oaded(object sender, EventArgs e)
{
var zoneCreator = new OrderZoneCreator();
IAsyncEnumerable<OrderZone> orderZones = zoneCreator.EnumerateOrderZonesAsync();
await foreach (OrderZone orderZone in orderZones)
{
this.OrderZones.Add(orderZone);
}
}
}
ZoneModelCreator.cs
public class OrderZoneCreator
{
public async IAsyncEnumerable<OrderZone> EnumerateOrderZonesAsync()
{
List<ZoneModel> zoneModels = await GetZonesAsync();
foreach (var zone in zoneModels)
{
var newOrderZone = new OrderZone(
zone._id,
zone.placement,
zone.name.tr,
zone.minBasketSize,
zone.status,
zone.restaurant);
yield return newOrderZone;
}
}
private async Task<List<ZoneModel>> GetZonesAsync()
{
string url = Constants.GET_ZONES;
var webClient = new WebClient<List<ZoneModel>>()
{
Endpoint = url,
Token = Token,
Method = Method.GET
};
var response = await webClient.SendToAPIAsync();
if (response.ErrorException != null)
{
// This is an internal operation and shouldn't generate user messages
// as the user can't do anything to make this operation succeed.
// Throw an exception if this error is not acceptable
// or do nothing and return an empty collection.
return new List<ZoneModel>();
}
return response.Data;
}
}
StatusToBooleanConverter.cs
class StatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is OrderZone orderZone
? orderZone.Status == 100
: Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
MainWindow.xaml
<Window>
<Window.Resources>
<local:StatusToBooleanConverter x:Key="StatusToBooleanConverter" />
</Window.Resources>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=OrderZones}"
BorderBrush="Black"
BorderThickness=2>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderZone}">
<CheckBox Content="{Binding Name, Mode OneTime}"
Tag="{Binding Id, Mode OneTime}"
Click="ZonesCheckBox_Click"
IsChecked="{Binding Status, Converter={StaticResource StatusToBooleanConverter}, Mode OneTime}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>