我在Silverlight应用程序中遇到了困难的情况。我的客户端必须从服务中检索三次查找表。一旦完全检索到所有三个集合,应用程序将继续。为简单起见,我只显示一个名为“Countries”的查找表的代码。
我想在另一个线程上运行每个服务调用,并使用ManualResetEvent []和WaitHandle.WaitAll()来同步线程。
但是由于Silverlight的异步特性,Service调用使用回调来告诉调用者现在已经加载了单个集合。我不能简单地让这个工作并且感到困惑。
这是检索国家/地区的服务电话: 的 SCMService:
private LoadOperation<Country> _allCountriesLoadOperation;
private Action<ObservableCollection<Country>> _getAllCountriesCallBack;
public void GetAllCountries(Action<ObservableCollection<Country>> getAllCountriesCallback)
{
Context.Countries.Clear();
var query = Context.GetCountriesQuery().OrderBy(c => c.CountryName);
_getAllCountriesCallBack = getAllCountriesCallback;
_allCountriesLoadOperation = Context.Load(query);
_allCountriesLoadOperation.Completed += OnLoadCountriesCompleted;
}
private void OnLoadCountriesCompleted(object sender, EventArgs e)
{
_allCountriesLoadOperation.Completed -= OnLoadCountriesCompleted;
var countries = new EntityList<Country>(Context.Countries, _allCountriesLoadOperation.Entities);
_getAllCountriesCallBack(countries);
}
这是我正在运行以调用上述服务的代码: 控制器类:
public void OnGetAllCountries()
{
_service.GetAllCountries(GetCountriesCallback);
}
private void GetCountriesCallback(ObservableCollection<Country> countries)
{
if (countries != null)
{
if (Countries == null)
{
Countries = countries;
_manualResetEvents[0].Set();
}
}
}
Controller()
{
//OnGetAllCountries(); This line alone would work fine. But I need to
//wait and make sure the callback is finished before continuing.
// Hence my multi threading approach below:
new Thread(OnGetAllCountries).Start();
WaitHandle.WaitAll(_manualResetEvents);
}
更新 我已经更新了代码以使其更清晰,如果在UI线程上运行代码和回调正在运行。但是如果我做了这个多线程,我的线程就会卡住。看代码评论
我做错了什么?我的策略是否有意义?
非常感谢,
答案 0 :(得分:0)
您的代码并不完全清楚,但似乎您试图通过执行WaitHandle.WaitAll
来阻止主Silverlight线程。你不能在Silverligt那样做。
相反,您应该从回调中更新用户界面的内容。您可以通过多种方式实现这一目标,使用数据绑定可能对您有所帮助。
您可以使用数据绑定到视图的视图模型,并通过独立回调进行更新。只有在执行第三个回调时,视图模型才会完全更新。这可以通过视图模型上的“就绪”属性发出信号,该属性可用于隐藏和显示用户界面的各个部分。
如果要在检索完所有数据之前“阻止”应用程序,则必须显示忙碌指示符(例如,在Silverlight Toolkit中找到)。然后,您应该在检索完所有数据后隐藏忙碌指示符。
如果您希望像现在一样处理服务,则需要从后台线程或任务调用服务。然后,此线程/任务可以以与您在代码中显示的方式相同的方式阻止,除非您在后台线程而不是主线程上执行此操作。当所有三个回调都已执行时,您可以从后台线程/任务更新用户界面。
如果您更喜欢使用线程而不是任务,我建议您使用线程池中的线程而不是创建自己的线程。
如果您不熟悉Silverlight编程模型的异步特性,我建议您通过示例代码了解如何从单个服务调用更新用户界面。然后,您可以按照与现在相同的方式将其扩展到许多服务调用。
答案 1 :(得分:0)
好的我认为您可能需要探索View Model模式以了解它应该如何处理这种情况,但如果我理解正确,您的问题的答案可能是以下策略。
忘记阻塞主线程 - 它不起作用,你引入一个显示的等待元素&amp;虽然显示其他程序元素不可访问,例如,直到指示所有对象都被加载的某个值为真,并且它将在一个单独的线程中计算,然后它将启动3个线程并将阻塞直到加载所有3个。
或者,如果您使用更清晰的ViewModel方法,则引入将加载的所有3个元素的组合的属性,例如
AllLoaded { get{return FirstLoaded && SecondLoaded && ThirdLoaded}} ,
并在这些FirstLoaded,SecondLoaded和ThirdLoaded的get方法中执行异步加载的集合,一旦它们加载了NotifyPropertyChanged,并检查是否需要通知AllLoaded也是,当所有3都设置为true时。一旦AllLoaded设置为true,您的wait元素就会隐藏,程序元素可以工作。
这种方法更多MVVM&amp; Silverlightish,但如果你不对主SL线程执行阻止,你的方法也可以工作。
答案 2 :(得分:0)
我正在做类似的事情,但使用ThreadPool.QueueUserWorkItem我加载了3个Scans,Actions和Steps对象集合。
在Loader类中我注册了3个实体的所有回调,并在那里设置了像这样的manualresetevents:
void Scans_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "ScanItems")
{
Debug.WriteLine("Scans Done =====");
manualEvents[(int)ItemDone.Scans].Set();
}
}
在加载器内部,我使用ThreadPool在另一个线程内等待所有内容:
manualEvents = new ManualResetEvent[3];
manualEvents[(int)ItemDone.Scans] = new ManualResetEvent(false);
manualEvents[(int)ItemDone.Actions] = new ManualResetEvent(false);
manualEvents[(int)ItemDone.AssySteps] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((obj) =>
{
Scans.Get(WipID, WC);
Actions.Get(WipID, WC);
AssySteps.Get(WipID, WC);
if (WaitHandle.WaitAll(manualEvents, new TimeSpan(0, 0, 5)))
{
int InstIndex = 0;
Instructions = new List<Instruction>();
AssySteps.AssyStepsItems.ForEach(s =>{Some code here....});
}
}
这种方式对我来说工作正常,我已经对它进行了基准测试,并且表现要好于按顺序执行。 这有帮助。问候。