场景,定时器6S一次调用以下方法:代码大概如下:
我另外的业务需要暂停这个定时器,当重新启动定时器时,因为需要即时,所以没用定时器启动方法,而直接调用这个方法,方法内部运行完再启动这个定时器,有时候很慢才进入UI线程里面,保存数据完成后开始计算UI线程进入事件,大概30秒以上。这是线程在排队吗?我应该怎么优化我的代码?或者说我能否指定一个线程专门负责我的定时器,而不需要每次都开启一个线程?
public void GetShipLocation(int min)
{
if (StopTimer)
{
return;
}
ThreadPool.QueueUserWorkItem((o) =>
{
Task.Run(async () =>
{
getShipPositionTimer.Stop();
try
{
Console.WriteLine("Testing method: GetShipLocation " + DateTime.Now);
//读取数据
//保存数据
await App.MyDatabase.SaveVesselRecordListAsync(App.vesselRecordList);
await App.MyDatabase.SaveListWarningMessAsync(newWarnList);
Application.Current.Dispatcher.Invoke(() =>
{
//一些UI线程对象操作
});
}
catch (Exception ex)
{
}
getShipPositionTimer.Start();
});
});
}
没看懂描述,
定时器6S一次调用GetShipLocation,
然后GetShipLocation里面又有对于定时器的Stop和Start?
时调用,但可能存在网络不好,或者说有时候线程进入慢的情况,所以把timer.stop和Start放在里面。
@DotNet8023
1 不要再timer的执行方法里在调用: timer.stop和start,不存在什么线程进入慢,是由于其他原因而导致的你认为的现象
个人认为是定时器触发方法的时候,你又手动触发了GetShipLocation方法,造成了SaveVesselRecordListAsync多次,而这个数据库连接是独占模式?
2 timmer即时停止,他的方法应该也会走完的
你的目的应该是
1 定时刷新界面
2 做了一些操作后,修改数据库,在即时刷新界面
现在的矛盾点在于定时刷新和即时刷新的冲突,用锁就能解决啊,
void RefreshUI()
{
Lock(object)
{
//UI操作
}
}
timmer:事件+GetShipLocation,执行就好,不需要停
另外的业务需要暂停这个定时器,重新启动定时器:
void DoSomethingAndRefreshUI()
{
await App.MyDatabase.SaveVesselRecordListAsync(App.vesselRecordList);
await App.MyDatabase.SaveListWarningMessAsync(newWarnList);
RefreshUI()
}
@猝不及防: 数据库是SQLite,是UI操作的时候加锁吗?这样?
void DoSomethingAndRefreshUI()
{
await App.MyDatabase.SaveVesselRecordListAsync(App.vesselRecordList);
await App.MyDatabase.SaveListWarningMessAsync(newWarnList);
Application.Current.Dispatcher.Invoke(() =>
{
Lock(object)
{
//UI操作
}
});
}
@DotNet8023:
对啊,但这不是重点
重点是你要把Timer.Start和Stop从Timer的执行方法分离出来,防止这任务开的越来越多
@DotNet8023: 修改了一下方法名,你应该好理解了
@DotNet8023:
其实按照wpf的思想 数据驱动界面 你是不需要手动更新ui的,只要你的数据变了界面就变了,
这样的话你只需定时从数据库中刷新绑定的数据就行,当然如果你还是一个刷新数据timmer,一个立即修改timmer的话绑定的属性也要加锁
@猝不及防: 说的UI操作是UI线程的对象操作。我也是说尽量按照 数据驱动界面 这样去做的,但有时候,比如一个用户控件只有一个Path,后台逻辑判断绑定什么形状的Data,判断填充什么颜色,我需要new几百个用户控件,当我操作这些path的数据时,比如原来的值是1,那就是红色,我修改了数据,改为2,应该是黄色,有时候不能即时在界面响应,或者说不会再次进入逻辑判断。
<Path Data="{Binding Vessel.Vessel, Converter={StaticResource CVTVesselModelTOPathData}}" x:Name="VesselPath"
Fill="{Binding Vessel , Converter={StaticResource CVTBoolTOSelectionFillRed}}"
Stroke="{Binding IsSelected, Converter={StaticResource CVTBoolTOSelectionForeColor}}"
StrokeThickness="{Binding IsMouseOver, Converter={StaticResource CVTIsMouseOverTOThickness}}" Cursor="Hand"
Opacity="{Binding Vessel.IsVisible, Converter={StaticResource CVTIsVisibleTOOpacity}}">
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Vessel.Vessel.Heading}" />
<TranslateTransform X="{Binding PositionX}" Y="{Binding PositionY}" />
</TransformGroup>
</Path.RenderTransform>
</Path>
@猝不及防: 我已经修改了我的代码,第一个定时器异步操作数据库,读取数据,第二个方法同步操作ui对象。没有用Task,延迟问题解决了。还是谢谢你。
QueueUserWorkItem 里面的代码必要再用Task了。可能连QueueUserWorkItem都不用因为看这代码读数据和保存数据的方法都是异步的。方法在保存完数据后才会执行更新UI的代码啊,你可以在读数据前和更新UI前记录下时间看看间隔时间
是操作完数据才会更新UI,没理解你说的
有一些问题
没异常,定时调用,但可能存在网络不好,或者说有时候线程进入慢的情况,所以把timer.stop和Start放在里面。
@DotNet8023: 个人建议
public async void GetShipLocation(int min)
{
if (StopTimer)
{
return;
}
getShipPositionTimer.Stop();
try
{
Console.WriteLine("Testing method: GetShipLocation " + DateTime.Now);
//读取数据
//保存数据
await App.MyDatabase.SaveVesselRecordListAsync(App.vesselRecordList);
await App.MyDatabase.SaveListWarningMessAsync(newWarnList);
Application.Current.Dispatcher.Invoke(() =>
{
//一些UI线程对象操作
});
}
catch (Exception ex)
{
}
getShipPositionTimer.Start();
}
@拓拓: 正常1S左右完成全部
@拓拓: 比如我这个方法正在运行到一半,我在别的地方暂停这个定时器,这个方法还是会继续往下走,尽管我在几个地方加了StopTimer判断,有时候还是会往下走。我应该如何阻止他继续往下走呢?
@DotNet8023: 如果1s左右,单凭这段代码不可能阻塞30秒时间,我看不出来有什么问题了。
一般来说,这种不怎么耗时的东西都是假装已经取消了。。。你想支持取消的话,那两个Save方法需要支持取消,用CancellationToken 和 CancellationTokenSource 来做
@拓拓: 感觉就像是线程排队
@DotNet8023: 问题是你要开多少后台任务才能排队30秒啊
@拓拓: 我是有两个开启线程的定时器,都是5/6秒一次,一个是调用接口获取数据的,一个是刚刚那个。如果运行半个小时以上就会出现延迟进入的问题
@DotNet8023: 5/6秒开启一个新线程吗? new Thread() ?
@拓拓: Task.Run(async () =>
@DotNet8023: Task.run 不会真的开启线程,开销不大,不是主要原因
最好做一个可运行的demo发上来
– 会长 4年前